* [v1,3/7] dt-bindings: stm32-mdma: Add DMA/MDMA chaining support bindings
From: Pierre Yves MORDRET @ 2018-09-11 7:26 UTC (permalink / raw)
To: Vinod Koul, Rob Herring, Mark Rutland, Alexandre Torgue,
Maxime Coquelin, Dan Williams, devicetree, dmaengine,
linux-arm-kernel, linux-kernel
Cc: Pierre-Yves MORDRET
This patch adds the description of the 2 properties needed to support M2M
transfer triggered by STM32 DMA when his transfer is complete.
Signed-off-by: Pierre-Yves MORDRET <pierre-yves.mordret@st.com>
---
Version history:
v1:
* Initial
---
---
.../devicetree/bindings/dma/stm32-mdma.txt | 22 +++++++++++++---------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/Documentation/devicetree/bindings/dma/stm32-mdma.txt b/Documentation/devicetree/bindings/dma/stm32-mdma.txt
index d18772d..1810f87 100644
--- a/Documentation/devicetree/bindings/dma/stm32-mdma.txt
+++ b/Documentation/devicetree/bindings/dma/stm32-mdma.txt
@@ -10,7 +10,7 @@ Required properties:
- interrupts: Should contain the MDMA interrupt.
- clocks: Should contain the input clock of the DMA instance.
- resets: Reference to a reset controller asserting the DMA controller.
-- #dma-cells : Must be <5>. See DMA client paragraph for more details.
+- #dma-cells : Must be <6>. See DMA client paragraph for more details.
Optional properties:
- dma-channels: Number of DMA channels supported by the controller.
@@ -26,7 +26,7 @@ Example:
interrupts = <122>;
clocks = <&timer_clk>;
resets = <&rcc 992>;
- #dma-cells = <5>;
+ #dma-cells = <6>;
dma-channels = <16>;
dma-requests = <32>;
st,ahb-addr-masks = <0x20000000>, <0x00000000>;
@@ -35,8 +35,8 @@ Example:
* DMA client
DMA clients connected to the STM32 MDMA controller must use the format
-described in the dma.txt file, using a five-cell specifier for each channel:
-a phandle to the MDMA controller plus the following five integer cells:
+described in the dma.txt file, using a six-cell specifier for each channel:
+a phandle to the MDMA controller plus the following six integer cells:
1. The request line number
2. The priority level
@@ -76,19 +76,23 @@ a phandle to the MDMA controller plus the following five integer cells:
if no HW ack signal is used by the MDMA client
5. A 32bit mask specifying the value to be written to acknowledge the request
if no HW ack signal is used by the MDMA client
+6. A bitfield value specifying if the MDMA client wants to generate M2M
+ transfer with HW trigger (1) or not (0). This bitfield should be only
+ enabled for M2M transfer triggered by STM32 DMA client. The memory devices
+ involved in this kind of transfer are SRAM and DDR.
Example:
i2c4: i2c@5c002000 {
compatible = "st,stm32f7-i2c";
reg = <0x5c002000 0x400>;
- interrupts = <95>,
- <96>;
- clocks = <&timer_clk>;
+ interrupts = <GIC_SPI 95 IRQ_TYPE_NONE>,
+ <GIC_SPI 96 IRQ_TYPE_NONE>;
+ clocks = <&clk_hsi>;
#address-cells = <1>;
#size-cells = <0>;
- dmas = <&mdma1 36 0x0 0x40008 0x0 0x0>,
- <&mdma1 37 0x0 0x40002 0x0 0x0>;
+ dmas = <&mdma1 36 0x0 0x40008 0x0 0x0 0>,
+ <&mdma1 37 0x0 0x40002 0x0 0x0 0>;
dma-names = "rx", "tx";
status = "disabled";
};
^ permalink raw reply related
* [v1,2/7] dt-bindings: stm32-dmamux: Add one cell to support DMA/MDMA chain
From: Pierre Yves MORDRET @ 2018-09-11 7:26 UTC (permalink / raw)
To: Vinod Koul, Rob Herring, Mark Rutland, Alexandre Torgue,
Maxime Coquelin, Dan Williams, devicetree, dmaengine,
linux-arm-kernel, linux-kernel
Cc: Pierre-Yves MORDRET
Add one cell to support DMA/MDMA chaining.
Signed-off-by: Pierre-Yves MORDRET <pierre-yves.mordret@st.com>
---
Version history:
v1:
* Initial
---
---
Documentation/devicetree/bindings/dma/stm32-dmamux.txt | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/Documentation/devicetree/bindings/dma/stm32-dmamux.txt b/Documentation/devicetree/bindings/dma/stm32-dmamux.txt
index 1b893b2..8e092d2 100644
--- a/Documentation/devicetree/bindings/dma/stm32-dmamux.txt
+++ b/Documentation/devicetree/bindings/dma/stm32-dmamux.txt
@@ -4,9 +4,6 @@ Required properties:
- compatible: "st,stm32h7-dmamux"
- reg: Memory map for accessing module
- #dma-cells: Should be set to <3>.
- First parameter is request line number.
- Second is DMA channel configuration
- Third is Fifo threshold
For more details about the three cells, please see
stm32-dma.txt documentation binding file
- dma-masters: Phandle pointing to the DMA controllers.
@@ -53,7 +50,7 @@ dma2: dma@40020400 {
<68>,
<69>,
<70>;
- clocks = <&timer_clk>;
+ clocks = <&clk_hclk>;
#dma-cells = <4>;
st,mem2mem;
resets = <&rcc 150>;
^ permalink raw reply related
* [v1,1/7] dt-bindings: stm32-dma: Add DMA/MDMA chaining support bindings
From: Pierre Yves MORDRET @ 2018-09-11 7:26 UTC (permalink / raw)
To: Vinod Koul, Rob Herring, Mark Rutland, Alexandre Torgue,
Maxime Coquelin, Dan Williams, devicetree, dmaengine,
linux-arm-kernel, linux-kernel
Cc: Pierre-Yves MORDRET
This patch adds dma bindings to support DMA/MDMA chaining transfer.
1 bit is to manage both DMA FIFO Threshold
1 bit is to manage DMA/MDMA Chaining features.
2 bits are used to specify SDRAM size to use for DMA/MDMA chaining.
The size in bytes of a certain order is given by the formula:
(2 ^ order) * PAGE_SIZE.
The order is given by those 2 bits.
For cyclic, whether chaining is chosen, any value above 1 can be set :
SRAM buffer size will rely on period size and not on this DT value.
Signed-off-by: Pierre-Yves MORDRET <pierre-yves.mordret@st.com>
---
Version history:
v1:
* Initial
---
---
.../devicetree/bindings/dma/stm32-dma.txt | 32 +++++++++++++++++++---
1 file changed, 28 insertions(+), 4 deletions(-)
diff --git a/Documentation/devicetree/bindings/dma/stm32-dma.txt b/Documentation/devicetree/bindings/dma/stm32-dma.txt
index c5f5190..163be09 100644
--- a/Documentation/devicetree/bindings/dma/stm32-dma.txt
+++ b/Documentation/devicetree/bindings/dma/stm32-dma.txt
@@ -17,6 +17,12 @@ Optional properties:
- resets: Reference to a reset controller asserting the DMA controller
- st,mem2mem: boolean; if defined, it indicates that the controller supports
memory-to-memory transfer
+- dmas: A list of eight dma specifiers, one for each entry in dma-names.
+ Refer to stm32-mdma.txt for more details.
+- dma-names: should contain "ch0", "ch1", "ch2", "ch3", "ch4", "ch5", "ch6" and
+ "ch7" and represents each STM32 DMA channel connected to a STM32 MDMA one.
+- memory-region : phandle to a node describing memory to be used for
+ M2M intermediate transfer between DMA and MDMA.
Example:
@@ -36,6 +42,16 @@ Example:
st,mem2mem;
resets = <&rcc 150>;
dma-requests = <8>;
+ dmas = <&mdma1 8 0x10 0x1200000a 0x40026408 0x00000020 1>,
+ <&mdma1 9 0x10 0x1200000a 0x40026408 0x00000800 1>,
+ <&mdma1 10 0x10 0x1200000a 0x40026408 0x00200000 1>,
+ <&mdma1 11 0x10 0x1200000a 0x40026408 0x08000000 1>,
+ <&mdma1 12 0x10 0x1200000a 0x4002640C 0x00000020 1>,
+ <&mdma1 13 0x10 0x1200000a 0x4002640C 0x00000800 1>,
+ <&mdma1 14 0x10 0x1200000a 0x4002640C 0x00200000 1>,
+ <&mdma1 15 0x10 0x1200000a 0x4002640C 0x08000000 1>;
+ dma-names = "ch0", "ch1", "ch2", "ch3", "ch4", "ch5", "ch6", "ch7";
+ memory-region = <&sram_dmapool>;
};
* DMA client
@@ -62,13 +78,21 @@ channel: a phandle to the DMA controller plus the following four integer cells:
0x1: medium
0x2: high
0x3: very high
-4. A 32bit bitfield value specifying DMA features which are device dependent:
+4. A bitfield value specifying DMA features which are device dependent:
-bit 0-1: DMA FIFO threshold selection
0x0: 1/4 full FIFO
0x1: 1/2 full FIFO
0x2: 3/4 full FIFO
0x3: full FIFO
-
+ -bit 2: Intermediate M2M transfer from/to DDR to/from SRAM throughout MDMA
+ 0: MDMA not used to generate an intermediate M2M transfer
+ 1: MDMA used to generate an intermediate M2M transfer.
+ -bit 3-4: indicated SRAM Buffer size in (2^order)*PAGE_SIZE.
+ Order is given by those 2 bits starting at 0.
+ Valid only whether Intermediate M2M transfer is set.
+ For cyclic, whether Intermediate M2M transfer is chosen, any value can
+ be set: SRAM buffer size will rely on period size and not on this DT
+ value.
Example:
@@ -77,7 +101,7 @@ Example:
reg = <0x40011000 0x400>;
interrupts = <37>;
clocks = <&clk_pclk2>;
- dmas = <&dma2 2 4 0x10400 0x3>,
- <&dma2 7 5 0x10200 0x3>;
+ dmas = <&dma2 2 4 0x10400 0x1>,
+ <&dma2 7 5 0x10200 0x1>;
dma-names = "rx", "tx";
};
^ permalink raw reply related
* [v2,2/2] dmaengine: uniphier-mdmac: add UniPhier MIO DMAC driver
From: Vinod Koul @ 2018-09-11 7:00 UTC (permalink / raw)
To: Masahiro Yamada
Cc: dmaengine, devicetree, Rob Herring, linux-kernel,
Masami Hiramatsu, Jassi Brar, Dan Williams, linux-arm-kernel
On 24-08-18, 10:41, Masahiro Yamada wrote:
> +/* mc->vc.lock must be held by caller */
> +static u32 __uniphier_mdmac_get_residue(struct uniphier_mdmac_desc *md)
> +{
> + u32 residue = 0;
> + int i;
> +
> + for (i = md->sg_cur; i < md->sg_len; i++)
> + residue += sg_dma_len(&md->sgl[i]);
so if the descriptor is submitted to hardware, we return the descriptor
length, which is not correct.
Two cases are required to be handled:
1. Descriptor is in queue (IMO above logic is fine for that, but it can
be calculated at descriptor submit and looked up here)
2. Descriptor is running (interesting case), you need to read current
register and offset that from descriptor length and return
> +static struct dma_async_tx_descriptor *uniphier_mdmac_prep_slave_sg(
> + struct dma_chan *chan,
> + struct scatterlist *sgl,
> + unsigned int sg_len,
> + enum dma_transfer_direction direction,
> + unsigned long flags, void *context)
> +{
> + struct virt_dma_chan *vc = to_virt_chan(chan);
> + struct uniphier_mdmac_desc *md;
> +
> + if (!is_slave_direction(direction))
> + return NULL;
> +
> + md = kzalloc(sizeof(*md), GFP_KERNEL);
_prep calls can be invoked from atomic context, so this should be
GFP_NOWAIT, see Documentation/driver-api/dmaengine/provider.rst
> + if (!md)
> + return NULL;
> +
> + md->sgl = sgl;
> + md->sg_len = sg_len;
> + md->dir = direction;
> +
> + return vchan_tx_prep(vc, &md->vd, flags);
this seems missing stuff. Where do you do register calculation for the
descriptor and where is slave_config here, how do you know where to
send/receive data form/to (peripheral)
> +static enum dma_status uniphier_mdmac_tx_status(struct dma_chan *chan,
> + dma_cookie_t cookie,
> + struct dma_tx_state *txstate)
> +{
> + struct virt_dma_chan *vc;
> + struct virt_dma_desc *vd;
> + struct uniphier_mdmac_chan *mc;
> + struct uniphier_mdmac_desc *md = NULL;
> + enum dma_status stat;
> + unsigned long flags;
> +
> + stat = dma_cookie_status(chan, cookie, txstate);
> + if (stat == DMA_COMPLETE)
> + return stat;
> +
> + vc = to_virt_chan(chan);
> +
> + spin_lock_irqsave(&vc->lock, flags);
> +
> + mc = to_uniphier_mdmac_chan(vc);
> +
> + if (mc->md && mc->md->vd.tx.cookie == cookie)
> + md = mc->md;
> +
> + if (!md) {
> + vd = vchan_find_desc(vc, cookie);
> + if (vd)
> + md = to_uniphier_mdmac_desc(vd);
> + }
> +
> + if (md)
> + txstate->residue = __uniphier_mdmac_get_residue(md);
txstate can be NULL and should be checked...
> +static int uniphier_mdmac_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct uniphier_mdmac_device *mdev;
> + struct dma_device *ddev;
> + struct resource *res;
> + int nr_chans, ret, i;
> +
> + nr_chans = platform_irq_count(pdev);
> + if (nr_chans < 0)
> + return nr_chans;
> +
> + ret = dma_set_mask(dev, DMA_BIT_MASK(32));
> + if (ret)
> + return ret;
> +
> + mdev = devm_kzalloc(dev, struct_size(mdev, channels, nr_chans),
> + GFP_KERNEL);
kcalloc variant?
> + if (!mdev)
> + return -ENOMEM;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + mdev->reg_base = devm_ioremap_resource(dev, res);
> + if (IS_ERR(mdev->reg_base))
> + return PTR_ERR(mdev->reg_base);
> +
> + mdev->clk = devm_clk_get(dev, NULL);
> + if (IS_ERR(mdev->clk)) {
> + dev_err(dev, "failed to get clock\n");
> + return PTR_ERR(mdev->clk);
> + }
> +
> + ret = clk_prepare_enable(mdev->clk);
> + if (ret)
> + return ret;
> +
> + ddev = &mdev->ddev;
> + ddev->dev = dev;
> + dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
> + ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED);
> + ddev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED);
> + ddev->directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM);
> + ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
> + ddev->device_prep_slave_sg = uniphier_mdmac_prep_slave_sg;
> + ddev->device_terminate_all = uniphier_mdmac_terminate_all;
> + ddev->device_synchronize = uniphier_mdmac_synchronize;
> + ddev->device_tx_status = uniphier_mdmac_tx_status;
> + ddev->device_issue_pending = uniphier_mdmac_issue_pending;
No device_config?
^ permalink raw reply
* [v10,1/4] dmaengine: fsl-edma: extract common fsl-edma code (no changes in behavior intended)
From: Vinod Koul @ 2018-09-11 6:37 UTC (permalink / raw)
To: Angelo Dureghello; +Cc: dmaengine, linux-arm-kernel, linux-m68k, stefan, krzk
On 19-08-18, 19:27, Angelo Dureghello wrote:
> This patch adds a new fsl-edma-common module to allow new
> mcf-edma module code to use most of the fsl-edma code.
Applied all, thanks
^ permalink raw reply
* dmaengine: Revert "dmaengine: add COMPILE_TEST for the drivers"
From: Vinod Koul @ 2018-09-11 6:09 UTC (permalink / raw)
To: dmaengine; +Cc: sjhuang, Vinod Koul
We have build failures attributed to turning on COMPILE_TEST, so revert
commit 90082cd397aeb: ("dmaengine: add COMPILE_TEST for the drivers")
while we fix these.
Signed-off-by: Vinod Koul <vkoul@kernel.org>
---
drivers/dma/Kconfig | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 73a75dea9cc3..dacf3f42426d 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -447,7 +447,7 @@ config PL330_DMA
config PXA_DMA
bool "PXA DMA support"
- depends on (ARCH_MMP || ARCH_PXA || COMPILE_TEST)
+ depends on (ARCH_MMP || ARCH_PXA)
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
help
@@ -465,7 +465,7 @@ config SIRF_DMA
config STE_DMA40
bool "ST-Ericsson DMA40 support"
- depends on ARCH_U8500 || COMPILE_TEST
+ depends on ARCH_U8500
select DMA_ENGINE
help
Support for ST-Ericsson DMA40 controller
^ permalink raw reply related
* [v5,3/7] dt-bindings: dmaengine: xilinx_dma: add optional xlnx,sg-length-width property
From: Rob Herring @ 2018-09-10 18:19 UTC (permalink / raw)
To: Andrea Merello
Cc: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine,
linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey
On Fri, 7 Sep 2018 08:24:58 +0200, Andrea Merello wrote:
> The width of the "length register" cannot be autodetected, and it is now
> specified with a DT property. Add documentation for it.
>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: devicetree@vger.kernel.org
> Cc: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
> Signed-off-by: Andrea Merello <andrea.merello@gmail.com>
> Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
> ---
> Changes in v2:
> - change property name
> - property is now optional
> - cc DT maintainer
> Changes in v3:
> - reword
> - cc DT maintainerS and ML
> Changes in v4:
> - specify the unit, the valid range and the default value
> Changes in v5:
> - commit message trivial fix
> - fix spaces before tab
> ---
> Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt | 4 ++++
> 1 file changed, 4 insertions(+)
>
Reviewed-by: Rob Herring <robh@kernel.org>
^ permalink raw reply
* dma: sh: convert to SPDX identifiers
From: Simon Horman @ 2018-09-10 13:10 UTC (permalink / raw)
To: Kuninori Morimoto
Cc: Vinod Koul, Linux-Renesas, Dan Williams, Geert Uytterhoeven,
dmaengine
On Fri, Sep 07, 2018 at 01:58:59AM +0000, Kuninori Morimoto wrote:
>
> From: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
>
> This patch updates license to use SPDX-License-Identifier
> instead of verbose license text.
>
> Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Simon Horman <horms+renesas@verge.net.au>
^ permalink raw reply
* [3/4] dmaengine: imx-sdma: implement channel termination via worker
From: Lucas Stach @ 2018-09-10 9:58 UTC (permalink / raw)
To: Robin Gong, Vinod Koul
Cc: dmaengine@vger.kernel.org, dl-linux-imx, kernel@pengutronix.de,
patchwork-lst@pengutronix.de
Am Dienstag, den 04.09.2018, 02:36 +0000 schrieb Robin Gong:
> > -----Original Message-----
> > From: Lucas Stach <l.stach@pengutronix.de>
> > Sent: 2018年9月3日 21:12
> > To: Robin Gong <yibin.gong@nxp.com>; Vinod Koul <vkoul@kernel.org>
> > Cc: dmaengine@vger.kernel.org; dl-linux-imx <linux-imx@nxp.com>;
> > kernel@pengutronix.de; patchwork-lst@pengutronix.de
> > Subject: Re: [PATCH 3/4] dmaengine: imx-sdma: implement channel
> > termination via worker
> >
> > Am Montag, den 03.09.2018, 08:59 +0000 schrieb Robin Gong:
> > > > -----Original Message-----
> > > > From: Lucas Stach <l.stach@pengutronix.de>
> > > > Sent: 2018年9月3日 16:41
> > > > To: Robin Gong <yibin.gong@nxp.com>; Vinod Koul <vkoul@kernel.o
> > > > rg>
> > > > Cc: dmaengine@vger.kernel.org; dl-linux-imx <linux-imx@nxp.com>
> > > > ;
> > > > kernel@pengutronix.de; patchwork-lst@pengutronix.de
> > > > Subject: Re: [PATCH 3/4] dmaengine: imx-sdma: implement channel
> > > > termination via worker
> > > >
> > > > Hi Robin,
> > > >
> > > > Am Freitag, den 31.08.2018, 09:49 +0000 schrieb Robin Gong:
> > > > > Hi Lucas,
> > > > > Seems I miss your previous mail. Thanks for your patch,
> > > > > but if
> > > > > move most jobs of sdma_disable_channel_with_delay() into
> > > > > worker,
> > > > > that will bring another race condition that upper driver such
> > > > > as
> > > > > Audio terminate channel and free resource of dma channel
> > > > > without
> > > > > really channel stop, if dma transfer done interrupt come
> > > > > after
> > > > > that, oops or kernel cash may be caught. Leave 'sdmac->desc =
> > > > > NULL' in the
> > > >
> > > > sdma_disable_channel_with_delay() may fix such potential issue.
> > > >
> > > > No, there is no such issue. The audio channel terminate will
> > > > call
> > > > dmaengine_terminate_sync(), which internally calls
> > > > dmaengine_terminate_async() and then does a
> > > > dmaengine_synchronize().
> > > > As this patchset implements the device_synchronize function in
> > > > the
> > > > sdma driver, this will wait for the worker to finish its
> > > > execution,
> > > > so there is no race condition to worry about here.
> > > >
> > > > Regards,
> > > > Lucas
> > >
> > > Yes, but how about other drivers which not call
> > > dmaengine_terminate_sync()?
> >
> > Please read the dmaengine documentation. device_terminate_all has
> > no
> > requirement that the transfer is actually canceled when the call
> > returns. If the
> > caller needs a guarantee that the channel is stopped it _must_ call
> > device_synchronize.
>
> I know that, but the fact is some driver still use
> dmaengine_terminate_all() such as
> Spi/uart driver. My concern is how to avoid to break their
> function.
They should simply be fixed to not use a deprecated function. Both of
those are only using device_terminate_all in error or shutdown paths,
so the risk of races is pretty minimal even with the current code. And
I think the SPI driver is trivial to fix, as we can just use the
terminate_sync variant there. The UART driver is a bit more tricky.
Regards,
Lucas
^ permalink raw reply
* [v7,3/7] dmaengine: fsl-qdma: Add qDMA controller driver for Layerscape SoCs
From: Li Yang @ 2018-09-07 19:28 UTC (permalink / raw)
To: Wen He
Cc: Vinod, dmaengine, Rob Herring,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Jiafei Pan, peng.ma
On Thu, Aug 16, 2018 at 10:13 AM Wen He <wen.he_1@nxp.com> wrote:
>
>
>
> > -----Original Message-----
> > From: dmaengine-owner@vger.kernel.org
> > [mailto:dmaengine-owner@vger.kernel.org] On Behalf Of Vinod
> > Sent: 2018年8月16日 12:39
> > To: Wen He <wen.he_1@nxp.com>
> > Cc: dmaengine@vger.kernel.org; robh+dt@kernel.org;
> > devicetree@vger.kernel.org; Leo Li <leoyang.li@nxp.com>; Jiafei Pan
> > <jiafei.pan@nxp.com>; Peng Ma <peng.ma@nxp.com>
> > Subject: Re: [v7 3/7] dmaengine: fsl-qdma: Add qDMA controller driver for
> > Layerscape SoCs
> >
> > On 15-08-18, 06:46, Wen He wrote:
> > >
> > >
> > > > -----Original Message-----
> > > > From: Wen He [mailto:wen.he_1@nxp.com]
> > > > Sent: 2018年7月25日 19:29
> > > > To: vkoul@kernel.org; dmaengine@vger.kernel.org
> > > > Cc: robh+dt@kernel.org; devicetree@vger.kernel.org; Leo Li
> > > > <leoyang.li@nxp.com>; Jiafei Pan <jiafei.pan@nxp.com>; Jiaheng Fan
> > > > <jiaheng.fan@nxp.com>; Wen He <wen.he_1@nxp.com>
> > > > Subject: [v7 3/7] dmaengine: fsl-qdma: Add qDMA controller driver
> > > > for Layerscape SoCs
> > > >
> > > > NXP Queue DMA controller(qDMA) on Layerscape SoCs supports channel
> > > > virtuallization by allowing DMA jobs to be enqueued into different
> > > > command queues.
> > > >
> > >
> > > Hi Vinod,
> > >
> > > Do you have any other comments for this patch?
> > > If not, can we merge it to upstream now?
> >
> > I don't have this in my queue, so can you please resend this after merge
> > window closes.
> >
> > Thanks
>
> OK, so I need resend all of patch, right?
Hi Wen He,
The merge window has already been closed. Can you resend the patches?
Regards,
Leo
^ permalink raw reply
* dma: idma64: replace spin_lock_irqsave with spin_lock
From: Zhaoxiong Yuan @ 2018-09-07 19:02 UTC (permalink / raw)
To: dan.j.williams, vkoul; +Cc: dmaengine, linux-kernel, Zhaoxiong Yuan
idma64_chan_irq() is invoked in hardirq handle function, it is unnecessary
to call spin_lock_irqsave.
Signed-off-by: Zhaoxiong Yuan <yuanzhx326@gmail.com>
---
drivers/dma/idma64.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/idma64.c b/drivers/dma/idma64.c
index 1fbf9cb..5b9c156 100644
--- a/drivers/dma/idma64.c
+++ b/drivers/dma/idma64.c
@@ -142,9 +142,8 @@ static void idma64_chan_irq(struct idma64 *idma64, unsigned short c,
{
struct idma64_chan *idma64c = &idma64->chan[c];
struct idma64_desc *desc;
- unsigned long flags;
- spin_lock_irqsave(&idma64c->vchan.lock, flags);
+ spin_lock(&idma64c->vchan.lock);
desc = idma64c->desc;
if (desc) {
if (status_err & (1 << c)) {
@@ -161,7 +160,7 @@ static void idma64_chan_irq(struct idma64 *idma64, unsigned short c,
if (idma64c->desc == NULL || desc->status == DMA_ERROR)
idma64_stop_transfer(idma64c);
}
- spin_unlock_irqrestore(&idma64c->vchan.lock, flags);
+ spin_unlock(&idma64c->vchan.lock);
}
static irqreturn_t idma64_irq(int irq, void *dev)
^ permalink raw reply related
* [3/3] dmaengine: xilinx_dma: Fix 64-bit simple CDMA transfer
From: Radhey Shyam Pandey @ 2018-09-07 12:08 UTC (permalink / raw)
To: Vinod
Cc: dan.j.williams@intel.com, Michal Simek,
Appana Durga Kedareswara Rao, lars@metafoo.de,
dmaengine@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
<snip>
> > > > > - xilinx_write(chan, XILINX_CDMA_REG_SRCADDR, hw-
> > > > >src_addr);
> > > > > - xilinx_write(chan, XILINX_CDMA_REG_DSTADDR, hw-
> > > > >dest_addr);
> > > > > + xilinx_write(chan, XILINX_CDMA_REG_SRCADDR,
> > > > (dma_addr_t)
> > > > > + ((u64)hw->src_addr_msb << 32 | hw-
> >src_addr));
> > > >
> > > > so this is:
> > > > (dma_addr_t)((u64)hw->src_addr_msb << 32 | hw->src_addr)
> > > >
> > > > what is src_addr data type? I think its u32. It would be better to
> > > > update xilinx_write() to take u64 and not dma_addr_t.
> > >
> > > Yes, src_addr_msb and src_addr BD fields are u32. To explain: There is no
> > > prob in xilinx_write it takes dma_addr_t as an arg which is 32/64 bit
> > > depending on _DMA_ADDR_T_64BIT. In 64bit CDMA transfer, there was a
> > bug
> > > i.e in the call to xilinx_write src_addr_msb 32 bits were not passed. To fix
> > > that combine MSB and LSB 32 bits before passing it to xilinx_write.
> >
> > Yeah that part was clear but the implementation can be better..
I thought over it and it seems having a new interface dma_ctrl_write_64
taking lsb and msb bits input looks better and scalable. It will be similar
to existing vdma_desc_write_64 impl. I will send v2 if it looks ok.
Thanks,
Radhey
<snip>
>
> >
> > --
> > ~Vinod
^ permalink raw reply
* [v5,7/7] dmaengine: xilinx_dma: Drop SG support for VDMA IP
From: Andrea Merello @ 2018-09-07 6:25 UTC (permalink / raw)
To: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine
Cc: linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey, Andrea Merello
xilinx_vdma_start_transfer() is used only for VDMA IP, still it contains
conditional code on has_sg variable. has_sg is set only whenever the HW
does support SG mode, that is never true for VDMA IP.
This patch drops the never-taken branches.
Signed-off-by: Andrea Merello <andrea.merello@gmail.com>
---
Changes in V4: introduced this patch in series
Changes in v5:
None
---
drivers/dma/xilinx/xilinx_dma.c | 84 +++++++++++++--------------------
1 file changed, 32 insertions(+), 52 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 78d0f2f8225e..07ceadef0a00 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -1093,6 +1093,8 @@ static void xilinx_vdma_start_transfer(struct xilinx_dma_chan *chan)
struct xilinx_dma_tx_descriptor *desc, *tail_desc;
u32 reg, j;
struct xilinx_vdma_tx_segment *tail_segment;
+ struct xilinx_vdma_tx_segment *segment, *last = NULL;
+ int i = 0;
/* This function was invoked with lock held */
if (chan->err)
@@ -1112,14 +1114,6 @@ static void xilinx_vdma_start_transfer(struct xilinx_dma_chan *chan)
tail_segment = list_last_entry(&tail_desc->segments,
struct xilinx_vdma_tx_segment, node);
- /*
- * If hardware is idle, then all descriptors on the running lists are
- * done, start new transfers
- */
- if (chan->has_sg)
- dma_ctrl_write(chan, XILINX_DMA_REG_CURDESC,
- desc->async_tx.phys);
-
/* Configure the hardware using info in the config structure */
reg = dma_ctrl_read(chan, XILINX_DMA_REG_DMACR);
@@ -1128,15 +1122,11 @@ static void xilinx_vdma_start_transfer(struct xilinx_dma_chan *chan)
else
reg &= ~XILINX_DMA_DMACR_FRAMECNT_EN;
- /*
- * With SG, start with circular mode, so that BDs can be fetched.
- * In direct register mode, if not parking, enable circular mode
- */
- if (chan->has_sg || !config->park)
- reg |= XILINX_DMA_DMACR_CIRC_EN;
-
+ /* If not parking, enable circular mode */
if (config->park)
reg &= ~XILINX_DMA_DMACR_CIRC_EN;
+ else
+ reg |= XILINX_DMA_DMACR_CIRC_EN;
dma_ctrl_write(chan, XILINX_DMA_REG_DMACR, reg);
@@ -1158,48 +1148,38 @@ static void xilinx_vdma_start_transfer(struct xilinx_dma_chan *chan)
return;
/* Start the transfer */
- if (chan->has_sg) {
- dma_ctrl_write(chan, XILINX_DMA_REG_TAILDESC,
- tail_segment->phys);
- list_splice_tail_init(&chan->pending_list, &chan->active_list);
- chan->desc_pendingcount = 0;
- } else {
- struct xilinx_vdma_tx_segment *segment, *last = NULL;
- int i = 0;
-
- if (chan->desc_submitcount < chan->num_frms)
- i = chan->desc_submitcount;
-
- list_for_each_entry(segment, &desc->segments, node) {
- if (chan->ext_addr)
- vdma_desc_write_64(chan,
- XILINX_VDMA_REG_START_ADDRESS_64(i++),
- segment->hw.buf_addr,
- segment->hw.buf_addr_msb);
- else
- vdma_desc_write(chan,
+ if (chan->desc_submitcount < chan->num_frms)
+ i = chan->desc_submitcount;
+
+ list_for_each_entry(segment, &desc->segments, node) {
+ if (chan->ext_addr)
+ vdma_desc_write_64(chan,
+ XILINX_VDMA_REG_START_ADDRESS_64(i++),
+ segment->hw.buf_addr,
+ segment->hw.buf_addr_msb);
+ else
+ vdma_desc_write(chan,
XILINX_VDMA_REG_START_ADDRESS(i++),
segment->hw.buf_addr);
- last = segment;
- }
-
- if (!last)
- return;
+ last = segment;
+ }
- /* HW expects these parameters to be same for one transaction */
- vdma_desc_write(chan, XILINX_DMA_REG_HSIZE, last->hw.hsize);
- vdma_desc_write(chan, XILINX_DMA_REG_FRMDLY_STRIDE,
- last->hw.stride);
- vdma_desc_write(chan, XILINX_DMA_REG_VSIZE, last->hw.vsize);
+ if (!last)
+ return;
- chan->desc_submitcount++;
- chan->desc_pendingcount--;
- list_del(&desc->node);
- list_add_tail(&desc->node, &chan->active_list);
- if (chan->desc_submitcount == chan->num_frms)
- chan->desc_submitcount = 0;
- }
+ /* HW expects these parameters to be same for one transaction */
+ vdma_desc_write(chan, XILINX_DMA_REG_HSIZE, last->hw.hsize);
+ vdma_desc_write(chan, XILINX_DMA_REG_FRMDLY_STRIDE,
+ last->hw.stride);
+ vdma_desc_write(chan, XILINX_DMA_REG_VSIZE, last->hw.vsize);
+
+ chan->desc_submitcount++;
+ chan->desc_pendingcount--;
+ list_del(&desc->node);
+ list_add_tail(&desc->node, &chan->active_list);
+ if (chan->desc_submitcount == chan->num_frms)
+ chan->desc_submitcount = 0;
chan->idle = false;
}
^ permalink raw reply related
* [v5,6/7] dt-bindings: dmaengine: xilinx_dma: drop has-sg property
From: Andrea Merello @ 2018-09-07 6:25 UTC (permalink / raw)
To: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine
Cc: linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey, Andrea Merello
This property is not needed anymore, because the driver now autodetects it.
Delete references in documentation.
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: devicetree@vger.kernel.org
Cc: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
Signed-off-by: Andrea Merello <andrea.merello@gmail.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
Changes in v2:
- cc DT maintainer
Changes in v3:
- cc DT maintainerS/ML
Changes in v4:
None
Changes in v5:
None
---
Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt | 3 ---
1 file changed, 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt b/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt
index 5df4eac7300c..6303ce7fcc3d 100644
--- a/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt
+++ b/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt
@@ -37,9 +37,6 @@ Required properties:
Required properties for VDMA:
- xlnx,num-fstores: Should be the number of framebuffers as configured in h/w.
-Optional properties:
-- xlnx,include-sg: Tells configured for Scatter-mode in
- the hardware.
Optional properties for AXI DMA:
- xlnx,sg-length-width: Should be set to the width in bits of the length
register as configured in h/w. Takes values {8...26}. If the property
^ permalink raw reply related
* [v5,5/7] dmaengine: xilinx_dma: autodetect whether the HW supports scatter-gather
From: Andrea Merello @ 2018-09-07 6:25 UTC (permalink / raw)
To: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine
Cc: linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey, Andrea Merello
The AXIDMA and CDMA HW can be either direct-access or scatter-gather
version. These are SW incompatible.
The driver can handle both versions: a DT property was used to
tell the driver whether to assume the HW is in scatter-gather mode.
This patch makes the driver to autodetect this information. The DT
property is not required anymore.
No changes for VDMA.
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: devicetree@vger.kernel.org
Cc: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
Signed-off-by: Andrea Merello <andrea.merello@gmail.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
---
Changes in v2:
- autodetect only in !VDMA case
Changes in v3:
- cc DT maintainers/ML
Changes in v4:
- fix typos in commit message
Changes in v5:
None
---
drivers/dma/xilinx/xilinx_dma.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index b17f24e4ec35..78d0f2f8225e 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -86,6 +86,7 @@
#define XILINX_DMA_DMASR_DMA_DEC_ERR BIT(6)
#define XILINX_DMA_DMASR_DMA_SLAVE_ERR BIT(5)
#define XILINX_DMA_DMASR_DMA_INT_ERR BIT(4)
+#define XILINX_DMA_DMASR_SG_MASK BIT(3)
#define XILINX_DMA_DMASR_IDLE BIT(1)
#define XILINX_DMA_DMASR_HALTED BIT(0)
#define XILINX_DMA_DMASR_DELAY_MASK GENMASK(31, 24)
@@ -407,7 +408,6 @@ struct xilinx_dma_config {
* @dev: Device Structure
* @common: DMA device structure
* @chan: Driver specific DMA channel
- * @has_sg: Specifies whether Scatter-Gather is present or not
* @mcdma: Specifies whether Multi-Channel is present or not
* @flush_on_fsync: Flush on frame sync
* @ext_addr: Indicates 64 bit addressing is supported by dma device
@@ -427,7 +427,6 @@ struct xilinx_dma_device {
struct device *dev;
struct dma_device common;
struct xilinx_dma_chan *chan[XILINX_DMA_MAX_CHANS_PER_DEVICE];
- bool has_sg;
bool mcdma;
u32 flush_on_fsync;
bool ext_addr;
@@ -2400,7 +2399,6 @@ static int xilinx_dma_chan_probe(struct xilinx_dma_device *xdev,
chan->dev = xdev->dev;
chan->xdev = xdev;
- chan->has_sg = xdev->has_sg;
chan->desc_pendingcount = 0x0;
chan->ext_addr = xdev->ext_addr;
/* This variable ensures that descriptors are not
@@ -2493,6 +2491,15 @@ static int xilinx_dma_chan_probe(struct xilinx_dma_device *xdev,
chan->stop_transfer = xilinx_dma_stop_transfer;
}
+ /* check if SG is enabled (only for AXIDMA and CDMA) */
+ if (xdev->dma_config->dmatype != XDMA_TYPE_VDMA) {
+ if (dma_ctrl_read(chan, XILINX_DMA_REG_DMASR) &
+ XILINX_DMA_DMASR_SG_MASK)
+ chan->has_sg = true;
+ dev_dbg(chan->dev, "ch %d: SG %s\n", chan->id,
+ chan->has_sg ? "enabled" : "disabled");
+ }
+
/* Initialize the tasklet */
tasklet_init(&chan->tasklet, xilinx_dma_do_tasklet,
(unsigned long)chan);
@@ -2631,7 +2638,6 @@ static int xilinx_dma_probe(struct platform_device *pdev)
return PTR_ERR(xdev->regs);
/* Retrieve the DMA engine properties from the device tree */
- xdev->has_sg = of_property_read_bool(node, "xlnx,include-sg");
xdev->max_buffer_len = GENMASK(XILINX_DMA_MAX_TRANS_LEN_MAX - 1, 0);
if (xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA) {
^ permalink raw reply related
* [v5,4/7] dmaengine: xilinx_dma: program hardware supported buffer length
From: Andrea Merello @ 2018-09-07 6:24 UTC (permalink / raw)
To: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine
Cc: linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey, Andrea Merello
From: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
AXI-DMA IP supports configurable (c_sg_length_width) buffer length
register width, hence read buffer length (xlnx,sg-length-width) DT
property and ensure that driver doesn't program buffer length
exceeding the supported limit. For VDMA and CDMA there is no change.
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: devicetree@vger.kernel.org
Signed-off-by: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
Signed-off-by: Andrea Merello <andrea.merello@gmail.com> [rebase, reword]
---
Changes in v2:
- drop original patch and replace with the one in Xilinx tree
Changes in v3:
- cc DT maintainers/ML
Changes in v4:
- upper bound for the property should be 26, not 23
- add warn for width > 23 as per xilinx original patch
- rework due to changes introduced in 1/6
Changes in v5:
None
---
drivers/dma/xilinx/xilinx_dma.c | 36 +++++++++++++++++++++++++--------
1 file changed, 28 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index aaa6de8a70e4..b17f24e4ec35 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -158,7 +158,9 @@
#define XILINX_DMA_REG_BTT 0x28
/* AXI DMA Specific Masks/Bit fields */
-#define XILINX_DMA_MAX_TRANS_LEN GENMASK(22, 0)
+#define XILINX_DMA_MAX_TRANS_LEN_MIN 8
+#define XILINX_DMA_MAX_TRANS_LEN_MAX 23
+#define XILINX_DMA_V2_MAX_TRANS_LEN_MAX 26
#define XILINX_DMA_CR_COALESCE_MAX GENMASK(23, 16)
#define XILINX_DMA_CR_CYCLIC_BD_EN_MASK BIT(4)
#define XILINX_DMA_CR_COALESCE_SHIFT 16
@@ -418,6 +420,7 @@ struct xilinx_dma_config {
* @rxs_clk: DMA s2mm stream clock
* @nr_channels: Number of channels DMA device supports
* @chan_id: DMA channel identifier
+ * @max_buffer_len: Max buffer length
*/
struct xilinx_dma_device {
void __iomem *regs;
@@ -437,6 +440,7 @@ struct xilinx_dma_device {
struct clk *rxs_clk;
u32 nr_channels;
u32 chan_id;
+ u32 max_buffer_len;
};
/* Macros */
@@ -964,7 +968,7 @@ static int xilinx_dma_calc_copysize(struct xilinx_dma_chan *chan,
int size, int done)
{
size_t copy = min_t(size_t, size - done,
- XILINX_DMA_MAX_TRANS_LEN);
+ chan->xdev->max_buffer_len);
if ((copy + done < size) &&
chan->xdev->common.copy_align) {
@@ -1011,7 +1015,7 @@ static enum dma_status xilinx_dma_tx_status(struct dma_chan *dchan,
list_for_each_entry(segment, &desc->segments, node) {
hw = &segment->hw;
residue += (hw->control - hw->status) &
- XILINX_DMA_MAX_TRANS_LEN;
+ chan->xdev->max_buffer_len;
}
}
spin_unlock_irqrestore(&chan->lock, flags);
@@ -1263,7 +1267,7 @@ static void xilinx_cdma_start_transfer(struct xilinx_dma_chan *chan)
/* Start the transfer */
dma_ctrl_write(chan, XILINX_DMA_REG_BTT,
- hw->control & XILINX_DMA_MAX_TRANS_LEN);
+ hw->control & chan->xdev->max_buffer_len);
}
list_splice_tail_init(&chan->pending_list, &chan->active_list);
@@ -1366,7 +1370,7 @@ static void xilinx_dma_start_transfer(struct xilinx_dma_chan *chan)
/* Start the transfer */
dma_ctrl_write(chan, XILINX_DMA_REG_BTT,
- hw->control & XILINX_DMA_MAX_TRANS_LEN);
+ hw->control & chan->xdev->max_buffer_len);
}
list_splice_tail_init(&chan->pending_list, &chan->active_list);
@@ -1727,7 +1731,7 @@ xilinx_cdma_prep_memcpy(struct dma_chan *dchan, dma_addr_t dma_dst,
struct xilinx_cdma_tx_segment *segment;
struct xilinx_cdma_desc_hw *hw;
- if (!len || len > XILINX_DMA_MAX_TRANS_LEN)
+ if (!len || len > chan->xdev->max_buffer_len)
return NULL;
desc = xilinx_dma_alloc_tx_descriptor(chan);
@@ -2596,7 +2600,7 @@ static int xilinx_dma_probe(struct platform_device *pdev)
struct xilinx_dma_device *xdev;
struct device_node *child, *np = pdev->dev.of_node;
struct resource *io;
- u32 num_frames, addr_width;
+ u32 num_frames, addr_width, len_width;
int i, err;
/* Allocate and initialize the DMA engine structure */
@@ -2628,8 +2632,24 @@ static int xilinx_dma_probe(struct platform_device *pdev)
/* Retrieve the DMA engine properties from the device tree */
xdev->has_sg = of_property_read_bool(node, "xlnx,include-sg");
- if (xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA)
+ xdev->max_buffer_len = GENMASK(XILINX_DMA_MAX_TRANS_LEN_MAX - 1, 0);
+
+ if (xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA) {
xdev->mcdma = of_property_read_bool(node, "xlnx,mcdma");
+ if (!of_property_read_u32(node, "xlnx,sg-length-width",
+ &len_width)) {
+ if (len_width < XILINX_DMA_MAX_TRANS_LEN_MIN ||
+ len_width > XILINX_DMA_V2_MAX_TRANS_LEN_MAX) {
+ dev_warn(xdev->dev,
+ "invalid xlnx,sg-length-width property value. Using default width\n");
+ } else {
+ if (len_width > XILINX_DMA_MAX_TRANS_LEN_MAX)
+ dev_warn(xdev->dev, "Please ensure that IP supports buffer length > 23 bits\n");
+ xdev->max_buffer_len =
+ GENMASK(len_width - 1, 0);
+ }
+ }
+ }
if (xdev->dma_config->dmatype == XDMA_TYPE_VDMA) {
err = of_property_read_u32(node, "xlnx,num-fstores",
^ permalink raw reply related
* [v5,3/7] dt-bindings: dmaengine: xilinx_dma: add optional xlnx,sg-length-width property
From: Andrea Merello @ 2018-09-07 6:24 UTC (permalink / raw)
To: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine
Cc: linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey, Andrea Merello
The width of the "length register" cannot be autodetected, and it is now
specified with a DT property. Add documentation for it.
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: devicetree@vger.kernel.org
Cc: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
Signed-off-by: Andrea Merello <andrea.merello@gmail.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
---
Changes in v2:
- change property name
- property is now optional
- cc DT maintainer
Changes in v3:
- reword
- cc DT maintainerS and ML
Changes in v4:
- specify the unit, the valid range and the default value
Changes in v5:
- commit message trivial fix
- fix spaces before tab
---
Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt b/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt
index a2b8bfaec43c..5df4eac7300c 100644
--- a/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt
+++ b/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt
@@ -41,6 +41,10 @@ Optional properties:
- xlnx,include-sg: Tells configured for Scatter-mode in
the hardware.
Optional properties for AXI DMA:
+- xlnx,sg-length-width: Should be set to the width in bits of the length
+ register as configured in h/w. Takes values {8...26}. If the property
+ is missing or invalid then the default value 23 is used. This is the
+ maximum value that is supported by all IP versions.
- xlnx,mcdma: Tells whether configured for multi-channel mode in the hardware.
Optional properties for VDMA:
- xlnx,flush-fsync: Tells which channel to Flush on Frame sync.
^ permalink raw reply related
* [v5,2/7] dmaengine: xilinx_dma: in axidma slave_sg and dma_cyclic mode align split descriptors
From: Andrea Merello @ 2018-09-07 6:24 UTC (permalink / raw)
To: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine
Cc: linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey, Andrea Merello
Whenever a single or cyclic transaction is prepared, the driver
could eventually split it over several SG descriptors in order
to deal with the HW maximum transfer length.
This could end up in DMA operations starting from a misaligned
address. This seems fatal for the HW if DRE (Data Realignment Engine)
is not enabled.
This patch eventually adjusts the transfer size in order to make sure
all operations start from an aligned address.
Cc: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
Signed-off-by: Andrea Merello <andrea.merello@gmail.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
---
Changes in v2:
- don't introduce copy_mask field, rather rely on already-esistent
copy_align field. Suggested by Radhey Shyam Pandey
- reword title
Changes in v3:
- fix bug introduced in v2: wrong copy size when DRE is enabled
- use implementation suggested by Radhey Shyam Pandey
Changes in v4:
- rework on the top of 1/6
Changes in v5:
- fix typo in commit title
- add hint about "DRE" meaning in commit message
---
drivers/dma/xilinx/xilinx_dma.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index a3aaa0e34cc7..aaa6de8a70e4 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -954,15 +954,28 @@ static int xilinx_dma_alloc_chan_resources(struct dma_chan *dchan)
/**
* xilinx_dma_calc_copysize - Calculate the amount of data to copy
+ * @chan: Driver specific DMA channel
* @size: Total data that needs to be copied
* @done: Amount of data that has been already copied
*
* Return: Amount of data that has to be copied
*/
-static int xilinx_dma_calc_copysize(int size, int done)
+static int xilinx_dma_calc_copysize(struct xilinx_dma_chan *chan,
+ int size, int done)
{
- return min_t(size_t, size - done,
+ size_t copy = min_t(size_t, size - done,
XILINX_DMA_MAX_TRANS_LEN);
+
+ if ((copy + done < size) &&
+ chan->xdev->common.copy_align) {
+ /*
+ * If this is not the last descriptor, make sure
+ * the next one will be properly aligned
+ */
+ copy = rounddown(copy,
+ (1 << chan->xdev->common.copy_align));
+ }
+ return copy;
}
/**
@@ -1804,7 +1817,7 @@ static struct dma_async_tx_descriptor *xilinx_dma_prep_slave_sg(
* Calculate the maximum number of bytes to transfer,
* making sure it is less than the hw limit
*/
- copy = xilinx_dma_calc_copysize(sg_dma_len(sg),
+ copy = xilinx_dma_calc_copysize(chan, sg_dma_len(sg),
sg_used);
hw = &segment->hw;
@@ -1909,7 +1922,8 @@ static struct dma_async_tx_descriptor *xilinx_dma_prep_dma_cyclic(
* Calculate the maximum number of bytes to transfer,
* making sure it is less than the hw limit
*/
- copy = xilinx_dma_calc_copysize(period_len, sg_used);
+ copy = xilinx_dma_calc_copysize(chan,
+ period_len, sg_used);
hw = &segment->hw;
xilinx_axidma_buf(chan, hw, buf_addr, sg_used,
period_len * i);
^ permalink raw reply related
* [v5,1/7] dmaengine: xilinx_dma: commonize DMA copy size calculation
From: Andrea Merello @ 2018-09-07 6:24 UTC (permalink / raw)
To: vkoul, dan.j.williams, michal.simek, appana.durga.rao, dmaengine
Cc: linux-arm-kernel, linux-kernel, robh+dt, mark.rutland, devicetree,
radhey.shyam.pandey, Andrea Merello
This patch removes a bit of duplicated code by introducing a new
function that implements calculations for DMA copy size.
Suggested-by: Vinod Koul <vkoul@kernel.org>
Signed-off-by: Andrea Merello <andrea.merello@gmail.com>
---
Changes in v4:
- introduce this patch in the patch series
Changes in v5:
None
---
drivers/dma/xilinx/xilinx_dma.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 27b523530c4a..a3aaa0e34cc7 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -952,6 +952,19 @@ static int xilinx_dma_alloc_chan_resources(struct dma_chan *dchan)
return 0;
}
+/**
+ * xilinx_dma_calc_copysize - Calculate the amount of data to copy
+ * @size: Total data that needs to be copied
+ * @done: Amount of data that has been already copied
+ *
+ * Return: Amount of data that has to be copied
+ */
+static int xilinx_dma_calc_copysize(int size, int done)
+{
+ return min_t(size_t, size - done,
+ XILINX_DMA_MAX_TRANS_LEN);
+}
+
/**
* xilinx_dma_tx_status - Get DMA transaction status
* @dchan: DMA channel
@@ -1791,8 +1804,8 @@ static struct dma_async_tx_descriptor *xilinx_dma_prep_slave_sg(
* Calculate the maximum number of bytes to transfer,
* making sure it is less than the hw limit
*/
- copy = min_t(size_t, sg_dma_len(sg) - sg_used,
- XILINX_DMA_MAX_TRANS_LEN);
+ copy = xilinx_dma_calc_copysize(sg_dma_len(sg),
+ sg_used);
hw = &segment->hw;
/* Fill in the descriptor */
@@ -1896,8 +1909,7 @@ static struct dma_async_tx_descriptor *xilinx_dma_prep_dma_cyclic(
* Calculate the maximum number of bytes to transfer,
* making sure it is less than the hw limit
*/
- copy = min_t(size_t, period_len - sg_used,
- XILINX_DMA_MAX_TRANS_LEN);
+ copy = xilinx_dma_calc_copysize(period_len, sg_used);
hw = &segment->hw;
xilinx_axidma_buf(chan, hw, buf_addr, sg_used,
period_len * i);
^ permalink raw reply related
* dma: sh: convert to SPDX identifiers
From: Kuninori Morimoto @ 2018-09-07 1:58 UTC (permalink / raw)
To: Vinod Koul
Cc: Linux-Renesas, Dan Williams, Geert Uytterhoeven, Simon Horman,
dmaengine
From: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
This patch updates license to use SPDX-License-Identifier
instead of verbose license text.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
---
drivers/dma/sh/Kconfig | 1 +
drivers/dma/sh/shdma-arm.h | 7 ++-----
drivers/dma/sh/shdma-base.c | 5 +----
drivers/dma/sh/shdma-of.c | 5 +----
drivers/dma/sh/shdma-r8a73a4.c | 5 +----
drivers/dma/sh/shdma.h | 9 ++-------
drivers/dma/sh/shdmac.c | 7 +------
drivers/dma/sh/sudmac.c | 5 +----
drivers/dma/sh/usb-dmac.c | 5 +----
include/linux/shdma-base.h | 7 ++-----
10 files changed, 13 insertions(+), 43 deletions(-)
diff --git a/drivers/dma/sh/Kconfig b/drivers/dma/sh/Kconfig
index 6e0685f..1c46754 100644
--- a/drivers/dma/sh/Kconfig
+++ b/drivers/dma/sh/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
#
# DMA engine configuration for sh
#
diff --git a/drivers/dma/sh/shdma-arm.h b/drivers/dma/sh/shdma-arm.h
index a1b0ef4..30bcfe3 100644
--- a/drivers/dma/sh/shdma-arm.h
+++ b/drivers/dma/sh/shdma-arm.h
@@ -1,11 +1,8 @@
-/*
+/* SPDX-License-Identifier: GPL-2.0
+ *
* Renesas SuperH DMA Engine support
*
* Copyright (C) 2013 Renesas Electronics, Inc.
- *
- * This is free software; you can redistribute it and/or modify it under the
- * terms of version 2 the GNU General Public License as published by the Free
- * Software Foundation.
*/
#ifndef SHDMA_ARM_H
diff --git a/drivers/dma/sh/shdma-base.c b/drivers/dma/sh/shdma-base.c
index 6b5626e..c51de49 100644
--- a/drivers/dma/sh/shdma-base.c
+++ b/drivers/dma/sh/shdma-base.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Dmaengine driver base library for DMA controllers, found on SH-based SoCs
*
@@ -7,10 +8,6 @@
* Copyright (C) 2009 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
* Copyright (C) 2009 Renesas Solutions, Inc. All rights reserved.
* Copyright (C) 2007 Freescale Semiconductor, Inc. All rights reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
*/
#include <linux/delay.h>
diff --git a/drivers/dma/sh/shdma-of.c b/drivers/dma/sh/shdma-of.c
index f999f9b..be89dd8 100644
--- a/drivers/dma/sh/shdma-of.c
+++ b/drivers/dma/sh/shdma-of.c
@@ -1,12 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* SHDMA Device Tree glue
*
* Copyright (C) 2013 Renesas Electronics Inc.
* Author: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
*/
#include <linux/dmaengine.h>
diff --git a/drivers/dma/sh/shdma-r8a73a4.c b/drivers/dma/sh/shdma-r8a73a4.c
index 96ea382..ddc9a35 100644
--- a/drivers/dma/sh/shdma-r8a73a4.c
+++ b/drivers/dma/sh/shdma-r8a73a4.c
@@ -1,11 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Renesas SuperH DMA Engine support for r8a73a4 (APE6) SoCs
*
* Copyright (C) 2013 Renesas Electronics, Inc.
- *
- * This is free software; you can redistribute it and/or modify it under the
- * terms of version 2 the GNU General Public License as published by the Free
- * Software Foundation.
*/
#include <linux/sh_dma.h>
diff --git a/drivers/dma/sh/shdma.h b/drivers/dma/sh/shdma.h
index 2c0a969..73aec72 100644
--- a/drivers/dma/sh/shdma.h
+++ b/drivers/dma/sh/shdma.h
@@ -1,14 +1,9 @@
-/*
+/* SPDX-License-Identifier: GPL-2.0+
+ *
* Renesas SuperH DMA Engine support
*
* Copyright (C) 2009 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
* Copyright (C) 2009 Renesas Solutions, Inc. All rights reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
*/
#ifndef __DMA_SHDMA_H
#define __DMA_SHDMA_H
diff --git a/drivers/dma/sh/shdmac.c b/drivers/dma/sh/shdmac.c
index 04a74e0..88b1eb8 100644
--- a/drivers/dma/sh/shdmac.c
+++ b/drivers/dma/sh/shdmac.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0+
/*
* Renesas SuperH DMA Engine support
*
@@ -8,14 +9,8 @@
* Copyright (C) 2009 Renesas Solutions, Inc. All rights reserved.
* Copyright (C) 2007 Freescale Semiconductor, Inc. All rights reserved.
*
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
* - DMA of SuperH does not have Hardware DMA chain mode.
* - MAX DMA size is 16MB.
- *
*/
#include <linux/delay.h>
diff --git a/drivers/dma/sh/sudmac.c b/drivers/dma/sh/sudmac.c
index 69b9564..30cc355 100644
--- a/drivers/dma/sh/sudmac.c
+++ b/drivers/dma/sh/sudmac.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Renesas SUDMAC support
*
@@ -8,10 +9,6 @@
* Copyright (C) 2009 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
* Copyright (C) 2009 Renesas Solutions, Inc. All rights reserved.
* Copyright (C) 2007 Freescale Semiconductor, Inc. All rights reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
*/
#include <linux/dmaengine.h>
diff --git a/drivers/dma/sh/usb-dmac.c b/drivers/dma/sh/usb-dmac.c
index 1bb1a8e..7f7184c 100644
--- a/drivers/dma/sh/usb-dmac.c
+++ b/drivers/dma/sh/usb-dmac.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Renesas USB DMA Controller Driver
*
@@ -6,10 +7,6 @@
* based on rcar-dmac.c
* Copyright (C) 2014 Renesas Electronics Inc.
* Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
*/
#include <linux/delay.h>
diff --git a/include/linux/shdma-base.h b/include/linux/shdma-base.h
index d927647..6dfd05e 100644
--- a/include/linux/shdma-base.h
+++ b/include/linux/shdma-base.h
@@ -1,4 +1,5 @@
-/*
+/* SPDX-License-Identifier: GPL-2.0
+ *
* Dmaengine driver base library for DMA controllers, found on SH-based SoCs
*
* extracted from shdma.c and headers
@@ -7,10 +8,6 @@
* Copyright (C) 2009 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
* Copyright (C) 2009 Renesas Solutions, Inc. All rights reserved.
* Copyright (C) 2007 Freescale Semiconductor, Inc. All rights reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
*/
#ifndef SHDMA_BASE_H
^ permalink raw reply related
* [2/2] dmaengine: mediatek: Add MediaTek Command-Queue DMA controller for MT6765 SoC
From: sean.wang @ 2018-09-05 9:13 UTC (permalink / raw)
To: shun-chih.yu
Cc: Vinod Koul, Rob Herring, Matthias Brugger, Dan Williams,
dmaengine, linux-arm-kernel, linux-mediatek, devicetree,
linux-kernel, srv_wsdupstream
On Tue, 2018-09-04 at 16:43 +0800, shun-chih.yu@mediatek.com wrote:
> From: Shun-Chih Yu <shun-chih.yu@mediatek.com>
>
> MediaTek Command-Queue DMA controller (CQDMA) on MT6765 SoC is dedicated
> to memory-to-memory transfer through queue based descriptor management.
>
> There are only 3 physical channels inside CQDMA, while the driver is
> extended to support 32 virtual channels for multiple dma users to issue
> dma requests onto the CQDMA simultaneously.
>
> Signed-off-by: Shun-Chih Yu <shun-chih.yu@mediatek.com>
> ---
> drivers/dma/mediatek/Kconfig | 12 +
> drivers/dma/mediatek/Makefile | 1 +
> drivers/dma/mediatek/mtk-cqdma.c | 952 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 965 insertions(+)
> create mode 100644 drivers/dma/mediatek/mtk-cqdma.c
>
> diff --git a/drivers/dma/mediatek/Kconfig b/drivers/dma/mediatek/Kconfig
> index 27bac0b..4a1582d 100644
> --- a/drivers/dma/mediatek/Kconfig
> +++ b/drivers/dma/mediatek/Kconfig
> @@ -11,3 +11,15 @@ config MTK_HSDMA
> This controller provides the channels which is dedicated to
> memory-to-memory transfer to offload from CPU through ring-
> based descriptor management.
> +
> +config MTK_CQDMA
> + tristate "MediaTek Command-Queue DMA controller support"
> + depends on ARCH_MEDIATEK || COMPILE_TEST
> + select DMA_ENGINE
> + select DMA_VIRTUAL_CHANNELS
> + help
> + Enable support for Command-Queue DMA controller on MediaTek
> + SoCs.
> +
> + This controller provides the channels which is dedicated to
> + memory-to-memory transfer to offload from CPU.
> diff --git a/drivers/dma/mediatek/Makefile b/drivers/dma/mediatek/Makefile
> index 6e778f8..41bb381 100644
> --- a/drivers/dma/mediatek/Makefile
> +++ b/drivers/dma/mediatek/Makefile
> @@ -1 +1,2 @@
> obj-$(CONFIG_MTK_HSDMA) += mtk-hsdma.o
> +obj-$(CONFIG_MTK_CQDMA) += mtk-cqdma.o
> diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
> new file mode 100644
> index 0000000..c74aaa3
> --- /dev/null
> +++ b/drivers/dma/mediatek/mtk-cqdma.c
> @@ -0,0 +1,952 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (c) 2018-2019 MediaTek Inc.
> +
> +/*
> + * Driver for MediaTek Command-Queue DMA Controller
> + *
> + * Author: Shun-Chih Yu <shun-chih.yu@mediatek.com>
> + *
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/iopoll.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_dma.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/refcount.h>
> +#include <linux/slab.h>
> +
> +#include "../virt-dma.h"
> +
> +#define MTK_CQDMA_USEC_POLL 10
> +#define MTK_CQDMA_TIMEOUT_POLL 1000
> +#define MTK_CQDMA_DMA_BUSWIDTHS BIT(DMA_SLAVE_BUSWIDTH_4_BYTES)
> +#define MTK_CQDMA_ALIGN_SIZE 1
> +
> +/* The default number of virtual channel */
> +#define MTK_CQDMA_NR_VCHANS 3
> +
commit message mentions there are 32 virtual channels available
> +/* The default number of physical channel */
> +#define MTK_CQDMA_NR_PCHANS 3
> +
> +/* Registers for underlying dma manipulation */
> +#define MTK_CQDMA_INT_FLAG 0x0
> +#define MTK_CQDMA_INT_EN 0x4
> +#define MTK_CQDMA_EN 0x8
> +#define MTK_CQDMA_RESET 0xc
> +#define MTK_CQDMA_STOP 0x10
> +#define MTK_CQDMA_FLUSH 0x14
> +#define MTK_CQDMA_SRC 0x1c
> +#define MTK_CQDMA_DST 0x20
> +#define MTK_CQDMA_LEN1 0x24
> +#define MTK_CQDMA_LEN2 0x28
> +#define MTK_CQDMA_SRC2 0x60
> +#define MTK_CQDMA_DST2 0x64
> +
> +/* Registers setting */
> +#define MTK_CQDMA_EN_BIT BIT(0)
> +#define MTK_CQDMA_INT_FLAG_BIT BIT(0)
> +#define MTK_CQDMA_INT_EN_BIT BIT(0)
> +#define MTK_CQDMA_FLUSH_BIT BIT(0)
> +
> +#define MTK_CQDMA_WARM_RST_BIT BIT(0)
> +#define MTK_CQDMA_HARD_RST_BIT BIT(1)
> +
> +#define MTK_CQDMA_MAX_LEN (0xfffffff)
> +#define MTK_CQDMA_ADDR_LIMIT (0xffffffff)
> +#define MTK_CQDMA_ADDR2_SHFIT (32)
remove these unused macros
> +
> +/**
> + * struct mtk_cqdma_vdesc - The struct holding info describing physical
> + * descriptor (PD)
> + * @len: The total data size device wants to move
> + * @src: The source address device wants to move from
> + * @dest: The destination address device wants to move to
> + */
> +struct mtk_cqdma_pdesc {
> + size_t len;
> + dma_addr_t src;
> + dma_addr_t dest;
> +};
> +
> +/**
> + * struct mtk_cqdma_vdesc - The struct holding info describing virtual
> + * descriptor (VD)
> + * @vd: An instance for struct virt_dma_desc
> + * @len: The total data size device wants to move
> + * @residue: The remaining data size device will move
> + * @dest: The destination address device wants to move to
> + * @src: The source address device wants to move from
> + * @ch: The pointer to the corresponding dma channel
> + * @pd_list The array for PDs
> + * @pd_list_len The size of PD list
> + * @pd_list_ptr The index of the PD being processed
> + * @node The lise_head struct to build link-list for VDs
> + */
> +struct mtk_cqdma_vdesc {
> + struct virt_dma_desc vd;
> + size_t len;
> + size_t residue;
> + dma_addr_t dest;
> + dma_addr_t src;
you already have src, dest, and len kept in cqdma_pdesc, i thought we can reuse them instead of holding another copy here
> + struct dma_chan *ch;
> +
> + size_t pd_list_len;
> + size_t pd_list_ptr;
> + struct mtk_cqdma_pdesc **pd_list;
> +
> + struct list_head node;
you create another list to hold descriptors in the driver,
in general, you can totally use list desc_[allocated, submitted, issued, and completed] vchan provides to mainatain the cycle of descriptors.
> +};
> +
> +/**
> + * struct mtk_cqdma_pchan - The struct holding info describing physical
> + * channel (PC)
> + * @queue: Queue for the PDs issued to this PC
> + * @base: The mapped register I/O base of this PC
> + * @irq: The IRQ that this PC are using
> + * @refcnt: Track how many VCs are using this PC
> + * @lock: Lock protect agaisting multiple VCs access PC
> + */
> +struct mtk_cqdma_pchan {
> + struct list_head queue;
> + void __iomem *base;
> + u32 irq;
> +
> + refcount_t refcnt;
> +
> + /* lock to protect PC */
> + spinlock_t lock;
> +};
> +
> +/**
> + * struct mtk_cqdma_vchan - The struct holding info describing virtual
> + * channel (VC)
> + * @vc: An instance for struct virt_dma_chan
> + * @pc: The pointer to the underlying PC
> + * @issue_completion: The wait for all issued descriptors completited
> + * @issue_synchronize: Bool indicating channel synchronization starts
> + */
> +struct mtk_cqdma_vchan {
> + struct virt_dma_chan vc;
> + struct mtk_cqdma_pchan *pc;
> + struct completion issue_completion;
> + bool issue_synchronize;
> +};
> +
> +/**
> + * struct mtk_cqdma_device - The struct holding info describing CQDMA
> + * device
> + * @ddev: An instance for struct dma_device
> + * @clk: The clock that device internal is using
> + * @dma_requests: The number of VCs the device supports to
> + * @dma_channels: The number of PCs the device supports to
> + * @vc: The pointer to all available VCs
> + * @pc: The pointer to all the underlying PCs
> + */
> +struct mtk_cqdma_device {
> + struct dma_device ddev;
> + struct clk *clk;
> +
> + u32 dma_requests;
> + u32 dma_channels;
> + struct mtk_cqdma_vchan *vc;
> + struct mtk_cqdma_pchan **pc;
> +};
> +
> +static struct mtk_cqdma_device *to_cqdma_dev(struct dma_chan *chan)
> +{
> + return container_of(chan->device, struct mtk_cqdma_device, ddev);
> +}
> +
> +static struct mtk_cqdma_vchan *to_cqdma_vchan(struct dma_chan *chan)
> +{
> + return container_of(chan, struct mtk_cqdma_vchan, vc.chan);
> +}
> +
> +static struct mtk_cqdma_vdesc *to_cqdma_vdesc(struct virt_dma_desc *vd)
> +{
> + return container_of(vd, struct mtk_cqdma_vdesc, vd);
> +}
> +
> +static struct device *cqdma2dev(struct mtk_cqdma_device *cqdma)
> +{
> + return cqdma->ddev.dev;
> +}
> +
> +static u32 mtk_dma_read(struct mtk_cqdma_pchan *pc, u32 reg)
> +{
> + return readl(pc->base + reg);
> +}
> +
> +static void mtk_dma_write(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
> +{
> + writel_relaxed(val, pc->base + reg);
> +}
> +
> +static void mtk_dma_rmw(struct mtk_cqdma_pchan *pc, u32 reg,
> + u32 mask, u32 set)
> +{
> + u32 val;
> +
> + val = mtk_dma_read(pc, reg);
> + val &= ~mask;
> + val |= set;
> + mtk_dma_write(pc, reg, val);
> +}
> +
> +static void mtk_dma_set(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
> +{
> + mtk_dma_rmw(pc, reg, 0, val);
> +}
> +
> +static void mtk_dma_clr(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
> +{
> + mtk_dma_rmw(pc, reg, val, 0);
> +}
> +
> +static void mtk_cqdma_vdesc_free(struct virt_dma_desc *vd)
> +{
> + struct mtk_cqdma_vdesc *cvd = to_cqdma_vdesc(vd);
> + size_t i;
> +
> + /* free PD list */
> + for (i = 0; i < cvd->pd_list_len; ++i)
> + kfree(cvd->pd_list[i]);
> + kfree(cvd->pd_list);
> +
> + /* free VD */
> + kfree(cvd);
> +}
> +
> +static int mtk_cqdma_poll_engine_done(struct mtk_cqdma_pchan *pc)
> +{
> + u32 status = 0;
> +
> + return readl_poll_timeout(pc->base + MTK_CQDMA_EN, status,
> + !(status & MTK_CQDMA_EN_BIT),
> + MTK_CQDMA_USEC_POLL,
> + MTK_CQDMA_TIMEOUT_POLL);
> +}
> +
> +static int mtk_cqdma_warm_reset(struct mtk_cqdma_pchan *pc)
> +{
> + mtk_dma_set(pc, MTK_CQDMA_RESET, MTK_CQDMA_WARM_RST_BIT);
> +
> + return mtk_cqdma_poll_engine_done(pc);
> +}
> +
> +static int mtk_cqdma_hard_reset(struct mtk_cqdma_pchan *pc)
> +{
> + mtk_dma_set(pc, MTK_CQDMA_RESET, MTK_CQDMA_HARD_RST_BIT);
> + mtk_dma_clr(pc, MTK_CQDMA_RESET, MTK_CQDMA_HARD_RST_BIT);
> +
> + return mtk_cqdma_poll_engine_done(pc);
> +}
> +
> +static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc)
> +{
> + mtk_dma_set(pc, MTK_CQDMA_EN, MTK_CQDMA_EN_BIT);
there is only a user for the function and the logic is quite simple, so lets merge into where the user is
> +}
> +
> +static int mtk_cqdma_stop(struct mtk_cqdma_pchan *pc)
> +{
> + int err;
> +
> + mtk_dma_set(pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
> +
> + err = mtk_cqdma_poll_engine_done(pc);
> +
> + mtk_dma_clr(pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
> + mtk_dma_clr(pc, MTK_CQDMA_INT_FLAG, MTK_CQDMA_INT_FLAG_BIT);
> +
> + return err;
there is only a user for the function and the logic is quite simple, so lets merge into where the user is
> +}
> +
> +static void mtk_cqdma_set_tran(struct mtk_cqdma_pchan *pc, dma_addr_t src,
> + dma_addr_t dest, size_t len)
> +{
> + /* setup source */
> + mtk_dma_set(pc, MTK_CQDMA_SRC, src & MTK_CQDMA_ADDR_LIMIT);
> + mtk_dma_set(pc, MTK_CQDMA_SRC2, src >> MTK_CQDMA_ADDR2_SHFIT);
> +
> + /* setup destination */
> + mtk_dma_set(pc, MTK_CQDMA_DST, dest & MTK_CQDMA_ADDR_LIMIT);
> + mtk_dma_set(pc, MTK_CQDMA_DST2, dest >> MTK_CQDMA_ADDR2_SHFIT);
> +
> + /* setup length */
> + mtk_dma_set(pc, MTK_CQDMA_LEN1, len);
there is only a user for the function and the logic is quite simple, so lets merge into where the user is
> +}
> +
> +static void mtk_cqdma_alloc_pchan(struct mtk_cqdma_pchan *pc)
> +{
> + /* hard reset the dma engine */
> + mtk_cqdma_hard_reset(pc);
> +
> + /* enable interrupt for this PC */
> + mtk_dma_set(pc, MTK_CQDMA_INT_EN, MTK_CQDMA_INT_EN_BIT);
there is only a user for the function and the logic is quite simple, so lets merge into where the user is
> +}
> +
> +static void mtk_cqdma_free_pchan(struct mtk_cqdma_pchan *pc)
> +{
> + /* stop the engine and wait for engine stop */
> + if (mtk_cqdma_stop(pc) < 0)
> + pr_warn("cqdma stop timeout\n");
dev_err
> + /* disable interrupt for this PC */
> + mtk_dma_clr(pc, MTK_CQDMA_INT_EN, MTK_CQDMA_INT_EN_BIT);
there is only a user for the function and the logic is quite simple, so lets merge into where the user is
> +}
> +
> +static void mtk_cqdma_start_tran(struct mtk_cqdma_pchan *pc,
> + struct mtk_cqdma_pdesc *cpd)
> +{
> + /* reset the dma engine for the transaction */
> + if (mtk_cqdma_warm_reset(pc) < 0)
> + pr_warn("cqdma warm reset timeout\n");
dev_err
> +
> + /* setup dma engine for this PD */
> + mtk_cqdma_set_tran(pc, cpd->src, cpd->dest, cpd->len);
> +
> + /* start dma engine */
> + mtk_cqdma_start(pc);
> +}
> +
> +static int mtk_cqdma_issue_pending_vdesc(struct mtk_cqdma_device *cqdma,
> + struct mtk_cqdma_pchan *pc,
> + struct mtk_cqdma_vdesc *cvd)
> +{
> + bool trigger_engine = false;
> +
> + if (!cvd->pd_list)
> + return 0;
> +
> + lockdep_assert_held(&pc->lock);
> +
> + /* need to trigger dma engine if PC's queue is empty */
> + if (list_empty(&pc->queue))
> + trigger_engine = true;
> +
> + /* add VD into PC's queue */
> + list_add_tail(&cvd->node, &pc->queue);
the hardware only can handle a descriptor at a time
so I thought the pc->queue seems complete no need, instead, you can just get a descriptor from ->desc_issued list to handle
, leave others descriptors still in ->desc_issued list until the the active descriptor finishes and then fire them
in sequence.
> +
> + /* start transaction for this VD */
> + if (trigger_engine)
> + mtk_cqdma_start_tran(pc, cvd->pd_list[cvd->pd_list_ptr]);
> +
> + return 0;
> +}
> +
> +static void mtk_cqdma_issue_vchan_pending(struct mtk_cqdma_device *cqdma,
> + struct mtk_cqdma_vchan *cvc)
> +{
> + struct virt_dma_desc *vd, *vd2;
> + int err;
> +
> + lockdep_assert_held(&cvc->vc.lock);
> +
> + list_for_each_entry_safe(vd, vd2, &cvc->vc.desc_issued, node) {
> + struct mtk_cqdma_vdesc *cvd;
> +
> + cvd = to_cqdma_vdesc(vd);
> +
> + /* issue VD to PC's queue */
> + err = mtk_cqdma_issue_pending_vdesc(cqdma, cvc->pc, cvd);
> +
> + if (err == -ENOSPC)
the error seems never happens
> + break;
> +
> + /* remove VD from list desc_issued */
> + list_del(&vd->node);
> + }
> +}
> +
> +/*
> + * return true if this VC is active,
> + * meaning that there are VDs under processing by the PC
> + */
> +static bool mtk_cqdma_is_vchan_active(struct mtk_cqdma_vchan *cvc)
> +{
> + struct mtk_cqdma_vdesc *cvd;
> +
> + list_for_each_entry(cvd, &cvc->pc->queue, node)
> + if (cvc == to_cqdma_vchan(cvd->ch))
> + return true;
> +
> + return false;
> +}
> +
> +static void mtk_cqdma_consume_work_queue(struct mtk_cqdma_pchan *pc)
> +{
> + struct mtk_cqdma_vchan *cvc;
> + struct mtk_cqdma_vdesc *cvd;
> +
> + /* consume a VD from queue */
> + cvd = list_first_entry_or_null(&pc->queue,
> + struct mtk_cqdma_vdesc, node);
> + if (unlikely(!cvd))
> + return;
> +
> + /* update residue of VD */
> + cvd->residue -= cvd->pd_list[cvd->pd_list_ptr]->len;
> +
> + cvc = to_cqdma_vchan(cvd->ch);
> +
> + if (cvd->pd_list_ptr == cvd->pd_list_len - 1) {
> + /* delete VD from queue if its PD list completed */
> + list_del(&cvd->node);
> +
> + spin_lock(&cvc->vc.lock);
> +
> + /* add VD into list desc_completed */
> + vchan_cookie_complete(&cvd->vd);
> +
> + /* setup completion if this VC is under synchronization */
> + if (cvc->issue_synchronize && !mtk_cqdma_is_vchan_active(cvc)) {
> + complete(&cvc->issue_completion);
> + cvc->issue_synchronize = false;
> + }
> +
> + spin_unlock(&cvc->vc.lock);
> + } else {
> + /* there are physical descs queueing to be served */
> + cvd->pd_list_ptr++;
> + }
> +
> + /* start transaction for next PD if queue is not empty */
> + cvd = list_first_entry_or_null(&pc->queue,
> + struct mtk_cqdma_vdesc, node);
> + if (cvd)
> + mtk_cqdma_start_tran(pc, cvd->pd_list[cvd->pd_list_ptr]);
I really thinks reuse desc_issued list can simplify the whole logic, otherwise you should
take care the synchronization between desc_completed list and pc->queue
> +}
> +
> +static irqreturn_t mtk_cqdma_irq(int irq, void *devid)
> +{
> + struct mtk_cqdma_device *cqdma = devid;
> + irqreturn_t ret = IRQ_NONE;
> + u32 i;
> +
> + /* clear interrupt flags for each PC */
> + for (i = 0; i < cqdma->dma_channels; ++i) {
> + spin_lock(&cqdma->pc[i]->lock);
> + if (mtk_dma_read(cqdma->pc[i],
> + MTK_CQDMA_INT_FLAG) & MTK_CQDMA_INT_FLAG_BIT) {
> + /* clear interrupt */
> + mtk_dma_clr(cqdma->pc[i], MTK_CQDMA_INT_FLAG,
> + MTK_CQDMA_INT_FLAG_BIT);
> +
> + /* consume the queue */
> + mtk_cqdma_consume_work_queue(cqdma->pc[i]);
> + ret = IRQ_HANDLED;
> + }
> + spin_unlock(&cqdma->pc[i]->lock);
> + }
> +
> + return ret;
> +}
> +
> +static struct virt_dma_desc *mtk_cqdma_find_active_desc(struct dma_chan *c,
> + dma_cookie_t cookie)
> +{
> + struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
> + struct virt_dma_desc *vd;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&cvc->pc->lock, flags);
> + list_for_each_entry(vd, &cvc->pc->queue, node)
> + if (vd->tx.cookie == cookie) {
> + spin_unlock_irqrestore(&cvc->pc->lock, flags);
> + return vd;
> + }
> + spin_unlock_irqrestore(&cvc->pc->lock, flags);
> +
> + list_for_each_entry(vd, &cvc->vc.desc_issued, node)
> + if (vd->tx.cookie == cookie)
> + return vd;
> +
> + return NULL;
> +}
> +
> +static enum dma_status mtk_cqdma_tx_status(struct dma_chan *c,
> + dma_cookie_t cookie,
> + struct dma_tx_state *txstate)
> +{
> + struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
> + struct mtk_cqdma_vdesc *cvd;
> + struct virt_dma_desc *vd;
> + enum dma_status ret;
> + unsigned long flags;
> + size_t bytes = 0;
> +
> + ret = dma_cookie_status(c, cookie, txstate);
> + if (ret == DMA_COMPLETE || !txstate)
> + return ret;
> +
> + spin_lock_irqsave(&cvc->vc.lock, flags);
> + vd = mtk_cqdma_find_active_desc(c, cookie);
> + spin_unlock_irqrestore(&cvc->vc.lock, flags);
> +
> + if (vd) {
> + cvd = to_cqdma_vdesc(vd);
> + bytes = cvd->residue;
> + }
> +
> + dma_set_residue(txstate, bytes);
> +
> + return ret;
> +}
> +
> +static void mtk_cqdma_issue_pending(struct dma_chan *c)
> +{
> + struct mtk_cqdma_device *cqdma = to_cqdma_dev(c);
> + struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
> + unsigned long pc_flags;
> + unsigned long vc_flags;
> +
> + /* acquire PC's lock first due to lock dependency in ISR */
> + spin_lock_irqsave(&cvc->pc->lock, pc_flags);
> + spin_lock_irqsave(&cvc->vc.lock, vc_flags);
> +
> + if (vchan_issue_pending(&cvc->vc))
> + mtk_cqdma_issue_vchan_pending(cqdma, cvc);
> +
> + spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
> + spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
> +}
> +
> +static struct dma_async_tx_descriptor *
> +mtk_cqdma_prep_dma_memcpy(struct dma_chan *c, dma_addr_t dest,
> + dma_addr_t src, size_t len, unsigned long flags)
> +{
> + struct mtk_cqdma_vdesc *cvd;
> + size_t pd_list_len, tlen, i;
> +
> + cvd = kzalloc(sizeof(*cvd), GFP_NOWAIT);
> + if (!cvd)
> + return NULL;
> +
> + /* setup dma channel */
> + cvd->ch = c;
> +
> + /* setup sourece, destination, and length */
> + cvd->len = len;
> + cvd->residue = len;
> + cvd->src = src;
> + cvd->dest = dest;
> +
> + /* setup PD list */
> + pd_list_len = DIV_ROUND_UP(len, MTK_CQDMA_MAX_LEN);
> + cvd->pd_list_len = pd_list_len;
> + cvd->pd_list_ptr = 0;
> +
> + cvd->pd_list = kcalloc(pd_list_len, sizeof(struct mtk_cqdma_pdesc **),
> + GFP_NOWAIT);
> + if (!cvd->pd_list) {
> + kfree(cvd);
> + return NULL;
> + }
> +
> + for (i = 0; i < pd_list_len; ++i) {
> + cvd->pd_list[i] = kzalloc(sizeof(struct mtk_cqdma_pdesc *),
> + GFP_NOWAIT);
> + if (!cvd->pd_list[i]) {
> + for (; i > 0; --i)
> + kfree(cvd->pd_list[i - 1]);
> + kfree(cvd->pd_list);
> + kfree(cvd);
> + return NULL;
> + }
> +
> + tlen = (len > MTK_CQDMA_MAX_LEN) ? MTK_CQDMA_MAX_LEN : len;
> +
> + cvd->pd_list[i]->src = cvd->src + cvd->len - tlen;
> + cvd->pd_list[i]->dest = cvd->dest + cvd->len - tlen;
> + cvd->pd_list[i]->len = tlen;
> + len -= tlen;
> + }
> +
> + return vchan_tx_prep(to_virt_chan(c), &cvd->vd, flags);
> +}
> +
> +static void mtk_cqdma_free_inactive_desc(struct dma_chan *c)
> +{
> + struct virt_dma_chan *vc = to_virt_chan(c);
> + unsigned long flags;
> + LIST_HEAD(head);
> +
> + /*
> + * set desc_allocated, desc_submitted,
> + * and desc_issued as the candicates to be freed
> + */
> + spin_lock_irqsave(&vc->lock, flags);
> + list_splice_tail_init(&vc->desc_allocated, &head);
> + list_splice_tail_init(&vc->desc_submitted, &head);
> + list_splice_tail_init(&vc->desc_issued, &head);
> + spin_unlock_irqrestore(&vc->lock, flags);
> +
> + /* free descriptor lists */
> + vchan_dma_desc_free_list(vc, &head);
> +}
> +
> +static void mtk_cqdma_free_active_desc(struct dma_chan *c)
> +{
> + struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
> + bool sync_needed = false;
> + unsigned long pc_flags;
> + unsigned long vc_flags;
> +
> + /* acquire PC's lock first due to lock dependency in dma ISR */
> + spin_lock_irqsave(&cvc->pc->lock, pc_flags);
> + spin_lock_irqsave(&cvc->vc.lock, vc_flags);
> +
> + /* synchronization is required if this VC is active */
> + if (mtk_cqdma_is_vchan_active(cvc)) {
> + cvc->issue_synchronize = true;
> + sync_needed = true;
> + }
> +
> + spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
> + spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
> +
> + /* waiting for the completion of this VC */
> + if (sync_needed)
> + wait_for_completion(&cvc->issue_completion);
> +
> + /* free all descriptors in list desc_completed */
> + vchan_synchronize(&cvc->vc);
> +
> + WARN_ONCE(!list_empty(&cvc->vc.desc_completed),
> + "Desc pending still in list desc_completed\n");
> +}
> +
> +static int mtk_cqdma_terminate_all(struct dma_chan *c)
> +{
> + /* free descriptors not processed yet by hardware */
> + mtk_cqdma_free_inactive_desc(c);
> +
> + /* free descriptors being processed by hardware */
> + mtk_cqdma_free_active_desc(c);
> +
> + return 0;
> +}
> +
> +static int mtk_cqdma_alloc_chan_resources(struct dma_chan *c)
> +{
> + struct mtk_cqdma_device *cqdma = to_cqdma_dev(c);
> + struct mtk_cqdma_vchan *vc = to_cqdma_vchan(c);
> + struct mtk_cqdma_pchan *pc = NULL;
> + u32 i, min_refcnt = U32_MAX, refcnt;
> + unsigned long flags;
> +
> + /* allocate PC with the minimun refcount */
> + for (i = 0; i < cqdma->dma_channels; ++i) {
> + refcnt = refcount_read(&cqdma->pc[i]->refcnt);
> + if (refcnt < min_refcnt) {
> + pc = cqdma->pc[i];
> + min_refcnt = refcnt;
> + }
> + }
> +
> + if (!pc)
> + return -ENOSPC;
> +
> + spin_lock_irqsave(&pc->lock, flags);
> +
> + if (!refcount_read(&pc->refcnt)) {
> + /* allocate PC when the refcount is zero */
> + mtk_cqdma_alloc_pchan(pc);
> + /*
> + * refcount_inc would complain increment on 0; use-after-free.
> + * Thus, we need to explicitly set it as 1 initially.
> + */
> + refcount_set(&pc->refcnt, 1);
> + } else {
> + refcount_inc(&pc->refcnt);
> + }
> +
> + spin_unlock_irqrestore(&pc->lock, flags);
> +
> + vc->pc = pc;
> +
> + return 0;
> +}
> +
> +static void mtk_cqdma_free_chan_resources(struct dma_chan *c)
> +{
> + struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
> + unsigned long flags;
> +
> + /* free all descriptors in all lists on the VC */
> + mtk_cqdma_terminate_all(c);
> +
> + spin_lock_irqsave(&cvc->pc->lock, flags);
> +
> + /* PC is not freed until there is no VC mapped to it */
> + if (refcount_dec_and_test(&cvc->pc->refcnt))
> + mtk_cqdma_free_pchan(cvc->pc);
> +
> + spin_unlock_irqrestore(&cvc->pc->lock, flags);
> +}
> +
> +static int mtk_cqdma_hw_init(struct mtk_cqdma_device *cqdma)
> +{
> + unsigned long flags;
> + int err;
> + u32 i;
> +
> + pm_runtime_enable(cqdma2dev(cqdma));
> + pm_runtime_get_sync(cqdma2dev(cqdma));
> +
> + err = clk_prepare_enable(cqdma->clk);
> +
> + if (err) {
> + pm_runtime_put_sync(cqdma2dev(cqdma));
> + pm_runtime_disable(cqdma2dev(cqdma));
> + return err;
> + }
> +
> + /* reset all PCs */
> + for (i = 0; i < cqdma->dma_channels; ++i) {
> + spin_lock_irqsave(&cqdma->pc[i]->lock, flags);
> + if (mtk_cqdma_hard_reset(cqdma->pc[i]) < 0) {
> + pr_warn("cqdma hard reset timeout\n");
> + spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
> +
> + clk_disable_unprepare(cqdma->clk);
> + pm_runtime_put_sync(cqdma2dev(cqdma));
> + pm_runtime_disable(cqdma2dev(cqdma));
> + return -EINVAL;
> + }
> + spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_cqdma_hw_deinit(struct mtk_cqdma_device *cqdma)
> +{
> + unsigned long flags;
> + u32 i;
> +
> + /* reset all PCs */
> + for (i = 0; i < cqdma->dma_channels; ++i) {
> + spin_lock_irqsave(&cqdma->pc[i]->lock, flags);
> + if (mtk_cqdma_hard_reset(cqdma->pc[i]) < 0)
> + pr_warn("cqdma hard reset timeout\n");
dev_err
> + spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
> + }
> +
> + clk_disable_unprepare(cqdma->clk);
> +
> + pm_runtime_put_sync(cqdma2dev(cqdma));
> + pm_runtime_disable(cqdma2dev(cqdma));
> +}
> +
> +static const struct of_device_id mtk_cqdma_match[] = {
> + { .compatible = "mediatek,mt6765-cqdma" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, mtk_cqdma_match);
> +
> +static int mtk_cqdma_probe(struct platform_device *pdev)
> +{
> + struct mtk_cqdma_device *cqdma;
> + struct mtk_cqdma_vchan *vc;
> + struct dma_device *dd;
> + struct resource *res;
> + int err;
> + u32 i;
> +
> + cqdma = devm_kzalloc(&pdev->dev, sizeof(*cqdma), GFP_KERNEL);
> + if (!cqdma)
> + return -ENOMEM;
> +
> + dd = &cqdma->ddev;
> +
> + cqdma->clk = devm_clk_get(&pdev->dev, "cqdma");
> + if (IS_ERR(cqdma->clk)) {
> + dev_err(&pdev->dev, "No clock for %s\n",
> + dev_name(&pdev->dev));
> + return PTR_ERR(cqdma->clk);
> + }
> +
> + dma_cap_set(DMA_MEMCPY, dd->cap_mask);
> +
> + dd->copy_align = MTK_CQDMA_ALIGN_SIZE;
> + dd->device_alloc_chan_resources = mtk_cqdma_alloc_chan_resources;
> + dd->device_free_chan_resources = mtk_cqdma_free_chan_resources;
> + dd->device_tx_status = mtk_cqdma_tx_status;
> + dd->device_issue_pending = mtk_cqdma_issue_pending;
> + dd->device_prep_dma_memcpy = mtk_cqdma_prep_dma_memcpy;
> + dd->device_terminate_all = mtk_cqdma_terminate_all;
> + dd->src_addr_widths = MTK_CQDMA_DMA_BUSWIDTHS;
> + dd->dst_addr_widths = MTK_CQDMA_DMA_BUSWIDTHS;
> + dd->directions = BIT(DMA_MEM_TO_MEM);
> + dd->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
> + dd->dev = &pdev->dev;
> + INIT_LIST_HEAD(&dd->channels);
> +
> + if (pdev->dev.of_node && of_property_read_u32(pdev->dev.of_node,
> + "dma-requests",
> + &cqdma->dma_requests)) {
> + dev_info(&pdev->dev,
> + "Using %u as missing dma-requests property\n",
> + MTK_CQDMA_NR_VCHANS);
> +
> + cqdma->dma_requests = MTK_CQDMA_NR_VCHANS;
> + }
> +
> + if (pdev->dev.of_node && of_property_read_u32(pdev->dev.of_node,
> + "dma-channels",
> + &cqdma->dma_channels)) {
> + dev_info(&pdev->dev,
> + "Using %u as missing dma-channels property\n",
> + MTK_CQDMA_NR_PCHANS);
> +
> + cqdma->dma_channels = MTK_CQDMA_NR_PCHANS;
> + }
> +
> + cqdma->pc = devm_kcalloc(&pdev->dev, cqdma->dma_channels,
> + sizeof(*cqdma->pc), GFP_KERNEL);
what happens when cqdma->dma_channels is more than MTK_CQDMA_NR_PCHANS ?
> + if (!cqdma->pc)
> + return -ENOMEM;
> +
> + /* initialization for PCs */
> + for (i = 0; i < cqdma->dma_channels; ++i) {
> + cqdma->pc[i] = devm_kcalloc(&pdev->dev, 1,
> + sizeof(**cqdma->pc), GFP_KERNEL);
> + if (!cqdma->pc[i])
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&cqdma->pc[i]->queue);
> + spin_lock_init(&cqdma->pc[i]->lock);
> + refcount_set(&cqdma->pc[i]->refcnt, 0);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, i);
> + if (!res) {
> + dev_err(&pdev->dev, "No mem resource for %s\n",
> + dev_name(&pdev->dev));
> + return -EINVAL;
> + }
> +
> + cqdma->pc[i]->base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(cqdma->pc[i]->base))
> + return PTR_ERR(cqdma->pc[i]->base);
> +
> + /* allocate IRQ resource */
> + res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
> + if (!res) {
> + dev_err(&pdev->dev, "No irq resource for %s\n",
> + dev_name(&pdev->dev));
> + return -EINVAL;
> + }
> + cqdma->pc[i]->irq = res->start;
> +
> + err = devm_request_irq(&pdev->dev, cqdma->pc[i]->irq,
> + mtk_cqdma_irq, 0, dev_name(&pdev->dev),
> + cqdma);
> + if (err) {
> + dev_err(&pdev->dev,
> + "request_irq failed with err %d\n", err);
> + return -EINVAL;
> + }
> + }
> +
> + /* allocate resource for VCs */
> + cqdma->vc = devm_kcalloc(&pdev->dev, cqdma->dma_requests,
> + sizeof(*cqdma->vc), GFP_KERNEL);
> + if (!cqdma->vc)
> + return -ENOMEM;
> +
> + for (i = 0; i < cqdma->dma_requests; i++) {
> + vc = &cqdma->vc[i];
> + vc->vc.desc_free = mtk_cqdma_vdesc_free;
> + vchan_init(&vc->vc, dd);
> + init_completion(&vc->issue_completion);
> + }
> +
> + err = dma_async_device_register(dd);
> + if (err)
> + return err;
> +
> + err = of_dma_controller_register(pdev->dev.of_node,
> + of_dma_xlate_by_chan_id, cqdma);
> + if (err) {
> + dev_err(&pdev->dev,
> + "MediaTek CQDMA OF registration failed %d\n", err);
> + goto err_unregister;
> + }
> +
> + err = mtk_cqdma_hw_init(cqdma);
> + if (err) {
> + dev_err(&pdev->dev,
> + "MediaTek CQDMA HW initialization failed %d\n", err);
> + goto err_unregister;
> + }
> +
> + platform_set_drvdata(pdev, cqdma);
> +
> + dev_info(&pdev->dev, "MediaTek CQDMA driver registered\n");
> +
> + return 0;
> +
> +err_unregister:
> + dma_async_device_unregister(dd);
> +
> + return err;
> +}
> +
> +static int mtk_cqdma_remove(struct platform_device *pdev)
> +{
> + struct mtk_cqdma_device *cqdma = platform_get_drvdata(pdev);
> + struct mtk_cqdma_vchan *vc;
> + unsigned long flags;
> + int i;
> +
> + /* kill VC task */
> + for (i = 0; i < cqdma->dma_requests; i++) {
> + vc = &cqdma->vc[i];
> +
> + list_del(&vc->vc.chan.device_node);
> + tasklet_kill(&vc->vc.task);
> + }
> +
> + /* disable interrupt */
> + for (i = 0; i < cqdma->dma_channels; i++) {
> + spin_lock_irqsave(&cqdma->pc[i]->lock, flags);
> + mtk_dma_clr(cqdma->pc[i], MTK_CQDMA_INT_EN,
> + MTK_CQDMA_INT_EN_BIT);
> + spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
> +
> + /* Waits for any pending IRQ handlers to complete */
> + synchronize_irq(cqdma->pc[i]->irq);
> + }
> +
> + /* disable hardware */
> + mtk_cqdma_hw_deinit(cqdma);
> +
> + dma_async_device_unregister(&cqdma->ddev);
> + of_dma_controller_free(pdev->dev.of_node);
> +
> + return 0;
> +}
> +
> +static struct platform_driver mtk_cqdma_driver = {
> + .probe = mtk_cqdma_probe,
> + .remove = mtk_cqdma_remove,
> + .driver = {
> + .name = KBUILD_MODNAME,
> + .of_match_table = mtk_cqdma_match,
> + },
> +};
> +module_platform_driver(mtk_cqdma_driver);
> +
> +MODULE_DESCRIPTION("MediaTek CQDMA Controller Driver");
> +MODULE_AUTHOR("Shun-Chih Yu <shun-chih.yu@mediatek.com>");
> +MODULE_LICENSE("GPL v2");
^ permalink raw reply
* [V2,5/5] dmaengine: Documentation: Add documentation for multi chan testing
From: Peter Ujfalusi @ 2018-09-04 19:15 UTC (permalink / raw)
To: Seraj Mohammed, Seraj Alijan, vkoul
Cc: dmaengine, dan.j.williams, james.hartley, sifan.naeem, ed.blake
Hi,
On 09/03/2018 05:49 PM, Seraj Mohammed wrote:
> Hi,
>
> On 03/09/18 09:19, Peter Ujfalusi wrote:
>> Hi,
>>
>> On 2018-08-31 18:01, Seraj Mohammed wrote:
>>>> However, if I got it right I need to reboot the machine if I want to
>>>> change any of the parameters for a re-run.
>>>> Let's say I want to run tests on 6 channel with 50K buffers, 100K 1M, 10M.
>>>> I can not do this without rebooting, right?
>>>
>>> You only need to reboot the machine if you want to change paameters after
>>> allocating the channels.
>>
>> Instead of reboot running the test is fine to clear things up.
>>
>>> Example:
>>>
>>> echo 4096 > /sys/module/dmatest/parameters/test_buf_size
>>> echo 1 > /sys/module/dmatest/parameters/iterations
>>> echo 6 > /sys/module/dmatest/parameters/alignment
>>> echo 0 > /sys/module/dmatest/parameters/noverify
>>> echo 0 > /sys/module/dmatest/parameters/norandom
>>> echo 1 > /sys/module/dmatest/parameters/threads_per_chan
>>> echo 20000 > /sys/module/dmatest/parameters/timeout
>>>
>>> At the is point, you can still change any of the parameters above without
>>> rebooting
>>
>> But I can not do:
>> echo 8000000 > /sys/module/dmatest/parameters/test_buf_size
>> echo 2000 > /sys/module/dmatest/parameters/timeout
>> echo 20 > /sys/module/dmatest/parameters/iterations
>> echo 20 > /sys/module/dmatest/parameters/max_channels
>> echo 1 > /sys/module/dmatest/parameters/run
>>
>> it will _not_ start 20 iteration tests on 20 channels.
>
> This is correct behavior, because issuing
> echo 1 > /sys/module/dmatest/parameters/run
> will no longer handle channel allocation with this patch, all
> functionality dealing with allocating channels has been stripped off
> from the run callback and put into a new callback that is triggered by
> the channel parameter.
>
> So under the new change, you can do the same by:
> echo 8000000 > /sys/module/dmatest/parameters/test_buf_size
> echo 2000 > /sys/module/dmatest/parameters/timeout
> echo 20 > /sys/module/dmatest/parameters/iterations
> echo 20 > /sys/module/dmatest/parameters/max_channels
> echo "" > /sys/module/dmatest/parameters/channel << Will trigger call
> back to allocate up to max_channels if invoked with empty string
> echo 1 > /sys/module/dmatest/parameters/run
Yep, I figured that out after trying this and that...
>> I need to:
>> echo dma0chan10 > /sys/module/dmatest/parameters/channel
>> cat /sys/module/dmatest/parameters/channel
>> dma0chan10
>>
>> echo dma0chan11 > /sys/module/dmatest/parameters/channel
>> cat /sys/module/dmatest/parameters/channel
>> dma0chan11
>>
>> ^ it tells me that only dma0chan11 is in channels while in fact I have
>> dma0chan10 and dma0chan11 selected.
>
> Correct, reading the channel parameter will report back the name of the
> LAST channel that was allocated, not a string containing all channels.
I think this is a bit awkward. What I would expect that it will give me the
list of channels I have already scheduled for the test. They can be in the
allocated state with different parameters, but that is minor thing.
>> then I run the test:
>> echo 1 > /sys/module/dmatest/parameters/run
>> [ 514.370959] dmatest: dma0chan10-copy: summary 1 tests, 0 failures
>> 7692 iops 15384 KB/s (0)
>> [ 514.371099] dmatest: dma0chan11-copy: summary 1 tests, 0 failures
>> 11111 iops 22222 KB/s (0)
>>
>> After this:
>> cat /sys/module/dmatest/parameters/channel returns nothing, which I
>> believe is correct. So I could be able to run the 20 iteration on 20
>> channels, but I can not:
>> echo 20 > /sys/module/dmatest/parameters/iterations
>> echo 20 > /sys/module/dmatest/parameters/max_channels
>> echo 1 > /sys/module/dmatest/parameters/run
>>
>> The patch effectively breaks the batch mode via max_channels.
>
> It doesn't matter if the channel string is empty, you need to invoke the
> channel parameter to allocate channels prior to running the test.
>
> This should work
>
> echo 20 > /sys/module/dmatest/parameters/iterations
> echo 20 > /sys/module/dmatest/parameters/max_channels
> echo "" > /sys/module/dmatest/parameters/channel
> echo 1 > /sys/module/dmatest/parameters/run
Yes, this works.
>>> As you can see now channels 0,1, 2 have already been allocated and their
>>> respective threads are pending. At this point if you want to change
>>> any parameters you have to reboot, because the channels have already been
>>> requested with the properties set above.
>>>
>>> echo 1 > /sys/module/dmatest/parameters/run
>>> dmatest: dma0chan0-copy0: summary 1 tests, 0 failures 285.71 iops 571
>>> KB/s (0)
>>> dmatest: dma0chan2-copy0: summary 1 tests, 0 failures 142.85 iops 285
>>> KB/s (0)
>>> dmatest: dma0chan1-copy0: summary 1 tests, 0 failures 142.85 iops 428
>>> KB/s (0)
>>>
>>> At this point the test is finished and you can run a new test with
>>> completely different parameters without having to reboot.
>>
>> That's fine.
>>
>> If I want to just repeat the same test then I would put the same
>> channels back in a loop and run it again to run them overnight for example.
>>
>> But it is still unclear how the channels will be released..
>> cat /sys/class/dma/dma0chan10/in_use
>> 0
>> echo dma0chan10 > /sys/module/dmatest/parameters/channel
>> cat /sys/class/dma/dma0chan10/in_use
>> 1
>> echo 1 > /sys/module/dmatest/parameters/run
>> [ 690.326039] dmatest: dma0chan10-copy: summary 20 tests, 0 failures 71
>> iops 290511 KB/s (0)
>> cat /sys/class/dma/dma0chan10/in_use
>> 1
>> echo 1 > /sys/module/dmatest/parameters/run
>> # will not run any tests
>> cat /sys/class/dma/dma0chan10/in_use
>> 1
>>
>> echo dma0chan12 > /sys/module/dmatest/parameters/channel
>> cat /sys/class/dma/dma0chan10/in_use
>> 0
>> cat /sys/class/dma/dma0chan12/in_use
>> 1
>>
>>
>> So it appears that the channel is going to be released when a new
>> channel is added, right?
>
> Correct, after completing a test, adding a new channel will clear all
> previously allocated channels to ensure the new test is not polluted
> with residual parameters from prior test runs.
>
> Another way of clearing channels from previous test run is to query the
> run parameter:
>
> cat /sys/module/dmatest/parameters/run
>
> should clear all channels allocated by previous test run.
Since the channels from the previous run will be discarded - even if in the
next run we configure the same channels, wouldn't it be nicer to release
everything when the test finishes to not hog on resources?
>> Which brought up some interesting thing:
>> echo "" > /sys/module/dmatest/parameters/channel
>>
>> Will add the 20 channels to the test :o
>>
>> One thing would be really nice is to print something when we hit run and
>> the dmatest actually going to do something or if the dmatest is not
>> configured properly and will not going to run any test.
>
> Good idea, adding a message informing user should clear up any confusion.
That would be great. The threads could print that they are starting or the
trigger code tells how many threads are going to be run, or if none is
configured tell that.
But I think the documentation should be updated to avoid a mail thread this
long to figure out what to expect and how to use the new dmatest ;)
^ permalink raw reply
* [2/2] dmaengine: mediatek: Add MediaTek Command-Queue DMA controller for MT6765 SoC
From: shun-chih.yu @ 2018-09-04 8:43 UTC (permalink / raw)
To: Sean Wang, Vinod Koul, Rob Herring, Matthias Brugger,
Dan Williams
Cc: dmaengine, linux-arm-kernel, linux-mediatek, devicetree,
linux-kernel, srv_wsdupstream, Shun-Chih Yu
From: Shun-Chih Yu <shun-chih.yu@mediatek.com>
MediaTek Command-Queue DMA controller (CQDMA) on MT6765 SoC is dedicated
to memory-to-memory transfer through queue based descriptor management.
There are only 3 physical channels inside CQDMA, while the driver is
extended to support 32 virtual channels for multiple dma users to issue
dma requests onto the CQDMA simultaneously.
Signed-off-by: Shun-Chih Yu <shun-chih.yu@mediatek.com>
---
drivers/dma/mediatek/Kconfig | 12 +
drivers/dma/mediatek/Makefile | 1 +
drivers/dma/mediatek/mtk-cqdma.c | 952 ++++++++++++++++++++++++++++++++++++++
3 files changed, 965 insertions(+)
create mode 100644 drivers/dma/mediatek/mtk-cqdma.c
diff --git a/drivers/dma/mediatek/Kconfig b/drivers/dma/mediatek/Kconfig
index 27bac0b..4a1582d 100644
--- a/drivers/dma/mediatek/Kconfig
+++ b/drivers/dma/mediatek/Kconfig
@@ -11,3 +11,15 @@ config MTK_HSDMA
This controller provides the channels which is dedicated to
memory-to-memory transfer to offload from CPU through ring-
based descriptor management.
+
+config MTK_CQDMA
+ tristate "MediaTek Command-Queue DMA controller support"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ Enable support for Command-Queue DMA controller on MediaTek
+ SoCs.
+
+ This controller provides the channels which is dedicated to
+ memory-to-memory transfer to offload from CPU.
diff --git a/drivers/dma/mediatek/Makefile b/drivers/dma/mediatek/Makefile
index 6e778f8..41bb381 100644
--- a/drivers/dma/mediatek/Makefile
+++ b/drivers/dma/mediatek/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_MTK_HSDMA) += mtk-hsdma.o
+obj-$(CONFIG_MTK_CQDMA) += mtk-cqdma.o
diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
new file mode 100644
index 0000000..c74aaa3
--- /dev/null
+++ b/drivers/dma/mediatek/mtk-cqdma.c
@@ -0,0 +1,952 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018-2019 MediaTek Inc.
+
+/*
+ * Driver for MediaTek Command-Queue DMA Controller
+ *
+ * Author: Shun-Chih Yu <shun-chih.yu@mediatek.com>
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+
+#include "../virt-dma.h"
+
+#define MTK_CQDMA_USEC_POLL 10
+#define MTK_CQDMA_TIMEOUT_POLL 1000
+#define MTK_CQDMA_DMA_BUSWIDTHS BIT(DMA_SLAVE_BUSWIDTH_4_BYTES)
+#define MTK_CQDMA_ALIGN_SIZE 1
+
+/* The default number of virtual channel */
+#define MTK_CQDMA_NR_VCHANS 3
+
+/* The default number of physical channel */
+#define MTK_CQDMA_NR_PCHANS 3
+
+/* Registers for underlying dma manipulation */
+#define MTK_CQDMA_INT_FLAG 0x0
+#define MTK_CQDMA_INT_EN 0x4
+#define MTK_CQDMA_EN 0x8
+#define MTK_CQDMA_RESET 0xc
+#define MTK_CQDMA_STOP 0x10
+#define MTK_CQDMA_FLUSH 0x14
+#define MTK_CQDMA_SRC 0x1c
+#define MTK_CQDMA_DST 0x20
+#define MTK_CQDMA_LEN1 0x24
+#define MTK_CQDMA_LEN2 0x28
+#define MTK_CQDMA_SRC2 0x60
+#define MTK_CQDMA_DST2 0x64
+
+/* Registers setting */
+#define MTK_CQDMA_EN_BIT BIT(0)
+#define MTK_CQDMA_INT_FLAG_BIT BIT(0)
+#define MTK_CQDMA_INT_EN_BIT BIT(0)
+#define MTK_CQDMA_FLUSH_BIT BIT(0)
+
+#define MTK_CQDMA_WARM_RST_BIT BIT(0)
+#define MTK_CQDMA_HARD_RST_BIT BIT(1)
+
+#define MTK_CQDMA_MAX_LEN (0xfffffff)
+#define MTK_CQDMA_ADDR_LIMIT (0xffffffff)
+#define MTK_CQDMA_ADDR2_SHFIT (32)
+
+/**
+ * struct mtk_cqdma_vdesc - The struct holding info describing physical
+ * descriptor (PD)
+ * @len: The total data size device wants to move
+ * @src: The source address device wants to move from
+ * @dest: The destination address device wants to move to
+ */
+struct mtk_cqdma_pdesc {
+ size_t len;
+ dma_addr_t src;
+ dma_addr_t dest;
+};
+
+/**
+ * struct mtk_cqdma_vdesc - The struct holding info describing virtual
+ * descriptor (VD)
+ * @vd: An instance for struct virt_dma_desc
+ * @len: The total data size device wants to move
+ * @residue: The remaining data size device will move
+ * @dest: The destination address device wants to move to
+ * @src: The source address device wants to move from
+ * @ch: The pointer to the corresponding dma channel
+ * @pd_list The array for PDs
+ * @pd_list_len The size of PD list
+ * @pd_list_ptr The index of the PD being processed
+ * @node The lise_head struct to build link-list for VDs
+ */
+struct mtk_cqdma_vdesc {
+ struct virt_dma_desc vd;
+ size_t len;
+ size_t residue;
+ dma_addr_t dest;
+ dma_addr_t src;
+ struct dma_chan *ch;
+
+ size_t pd_list_len;
+ size_t pd_list_ptr;
+ struct mtk_cqdma_pdesc **pd_list;
+
+ struct list_head node;
+};
+
+/**
+ * struct mtk_cqdma_pchan - The struct holding info describing physical
+ * channel (PC)
+ * @queue: Queue for the PDs issued to this PC
+ * @base: The mapped register I/O base of this PC
+ * @irq: The IRQ that this PC are using
+ * @refcnt: Track how many VCs are using this PC
+ * @lock: Lock protect agaisting multiple VCs access PC
+ */
+struct mtk_cqdma_pchan {
+ struct list_head queue;
+ void __iomem *base;
+ u32 irq;
+
+ refcount_t refcnt;
+
+ /* lock to protect PC */
+ spinlock_t lock;
+};
+
+/**
+ * struct mtk_cqdma_vchan - The struct holding info describing virtual
+ * channel (VC)
+ * @vc: An instance for struct virt_dma_chan
+ * @pc: The pointer to the underlying PC
+ * @issue_completion: The wait for all issued descriptors completited
+ * @issue_synchronize: Bool indicating channel synchronization starts
+ */
+struct mtk_cqdma_vchan {
+ struct virt_dma_chan vc;
+ struct mtk_cqdma_pchan *pc;
+ struct completion issue_completion;
+ bool issue_synchronize;
+};
+
+/**
+ * struct mtk_cqdma_device - The struct holding info describing CQDMA
+ * device
+ * @ddev: An instance for struct dma_device
+ * @clk: The clock that device internal is using
+ * @dma_requests: The number of VCs the device supports to
+ * @dma_channels: The number of PCs the device supports to
+ * @vc: The pointer to all available VCs
+ * @pc: The pointer to all the underlying PCs
+ */
+struct mtk_cqdma_device {
+ struct dma_device ddev;
+ struct clk *clk;
+
+ u32 dma_requests;
+ u32 dma_channels;
+ struct mtk_cqdma_vchan *vc;
+ struct mtk_cqdma_pchan **pc;
+};
+
+static struct mtk_cqdma_device *to_cqdma_dev(struct dma_chan *chan)
+{
+ return container_of(chan->device, struct mtk_cqdma_device, ddev);
+}
+
+static struct mtk_cqdma_vchan *to_cqdma_vchan(struct dma_chan *chan)
+{
+ return container_of(chan, struct mtk_cqdma_vchan, vc.chan);
+}
+
+static struct mtk_cqdma_vdesc *to_cqdma_vdesc(struct virt_dma_desc *vd)
+{
+ return container_of(vd, struct mtk_cqdma_vdesc, vd);
+}
+
+static struct device *cqdma2dev(struct mtk_cqdma_device *cqdma)
+{
+ return cqdma->ddev.dev;
+}
+
+static u32 mtk_dma_read(struct mtk_cqdma_pchan *pc, u32 reg)
+{
+ return readl(pc->base + reg);
+}
+
+static void mtk_dma_write(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
+{
+ writel_relaxed(val, pc->base + reg);
+}
+
+static void mtk_dma_rmw(struct mtk_cqdma_pchan *pc, u32 reg,
+ u32 mask, u32 set)
+{
+ u32 val;
+
+ val = mtk_dma_read(pc, reg);
+ val &= ~mask;
+ val |= set;
+ mtk_dma_write(pc, reg, val);
+}
+
+static void mtk_dma_set(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
+{
+ mtk_dma_rmw(pc, reg, 0, val);
+}
+
+static void mtk_dma_clr(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
+{
+ mtk_dma_rmw(pc, reg, val, 0);
+}
+
+static void mtk_cqdma_vdesc_free(struct virt_dma_desc *vd)
+{
+ struct mtk_cqdma_vdesc *cvd = to_cqdma_vdesc(vd);
+ size_t i;
+
+ /* free PD list */
+ for (i = 0; i < cvd->pd_list_len; ++i)
+ kfree(cvd->pd_list[i]);
+ kfree(cvd->pd_list);
+
+ /* free VD */
+ kfree(cvd);
+}
+
+static int mtk_cqdma_poll_engine_done(struct mtk_cqdma_pchan *pc)
+{
+ u32 status = 0;
+
+ return readl_poll_timeout(pc->base + MTK_CQDMA_EN, status,
+ !(status & MTK_CQDMA_EN_BIT),
+ MTK_CQDMA_USEC_POLL,
+ MTK_CQDMA_TIMEOUT_POLL);
+}
+
+static int mtk_cqdma_warm_reset(struct mtk_cqdma_pchan *pc)
+{
+ mtk_dma_set(pc, MTK_CQDMA_RESET, MTK_CQDMA_WARM_RST_BIT);
+
+ return mtk_cqdma_poll_engine_done(pc);
+}
+
+static int mtk_cqdma_hard_reset(struct mtk_cqdma_pchan *pc)
+{
+ mtk_dma_set(pc, MTK_CQDMA_RESET, MTK_CQDMA_HARD_RST_BIT);
+ mtk_dma_clr(pc, MTK_CQDMA_RESET, MTK_CQDMA_HARD_RST_BIT);
+
+ return mtk_cqdma_poll_engine_done(pc);
+}
+
+static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc)
+{
+ mtk_dma_set(pc, MTK_CQDMA_EN, MTK_CQDMA_EN_BIT);
+}
+
+static int mtk_cqdma_stop(struct mtk_cqdma_pchan *pc)
+{
+ int err;
+
+ mtk_dma_set(pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
+
+ err = mtk_cqdma_poll_engine_done(pc);
+
+ mtk_dma_clr(pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
+ mtk_dma_clr(pc, MTK_CQDMA_INT_FLAG, MTK_CQDMA_INT_FLAG_BIT);
+
+ return err;
+}
+
+static void mtk_cqdma_set_tran(struct mtk_cqdma_pchan *pc, dma_addr_t src,
+ dma_addr_t dest, size_t len)
+{
+ /* setup source */
+ mtk_dma_set(pc, MTK_CQDMA_SRC, src & MTK_CQDMA_ADDR_LIMIT);
+ mtk_dma_set(pc, MTK_CQDMA_SRC2, src >> MTK_CQDMA_ADDR2_SHFIT);
+
+ /* setup destination */
+ mtk_dma_set(pc, MTK_CQDMA_DST, dest & MTK_CQDMA_ADDR_LIMIT);
+ mtk_dma_set(pc, MTK_CQDMA_DST2, dest >> MTK_CQDMA_ADDR2_SHFIT);
+
+ /* setup length */
+ mtk_dma_set(pc, MTK_CQDMA_LEN1, len);
+}
+
+static void mtk_cqdma_alloc_pchan(struct mtk_cqdma_pchan *pc)
+{
+ /* hard reset the dma engine */
+ mtk_cqdma_hard_reset(pc);
+
+ /* enable interrupt for this PC */
+ mtk_dma_set(pc, MTK_CQDMA_INT_EN, MTK_CQDMA_INT_EN_BIT);
+}
+
+static void mtk_cqdma_free_pchan(struct mtk_cqdma_pchan *pc)
+{
+ /* stop the engine and wait for engine stop */
+ if (mtk_cqdma_stop(pc) < 0)
+ pr_warn("cqdma stop timeout\n");
+
+ /* disable interrupt for this PC */
+ mtk_dma_clr(pc, MTK_CQDMA_INT_EN, MTK_CQDMA_INT_EN_BIT);
+}
+
+static void mtk_cqdma_start_tran(struct mtk_cqdma_pchan *pc,
+ struct mtk_cqdma_pdesc *cpd)
+{
+ /* reset the dma engine for the transaction */
+ if (mtk_cqdma_warm_reset(pc) < 0)
+ pr_warn("cqdma warm reset timeout\n");
+
+ /* setup dma engine for this PD */
+ mtk_cqdma_set_tran(pc, cpd->src, cpd->dest, cpd->len);
+
+ /* start dma engine */
+ mtk_cqdma_start(pc);
+}
+
+static int mtk_cqdma_issue_pending_vdesc(struct mtk_cqdma_device *cqdma,
+ struct mtk_cqdma_pchan *pc,
+ struct mtk_cqdma_vdesc *cvd)
+{
+ bool trigger_engine = false;
+
+ if (!cvd->pd_list)
+ return 0;
+
+ lockdep_assert_held(&pc->lock);
+
+ /* need to trigger dma engine if PC's queue is empty */
+ if (list_empty(&pc->queue))
+ trigger_engine = true;
+
+ /* add VD into PC's queue */
+ list_add_tail(&cvd->node, &pc->queue);
+
+ /* start transaction for this VD */
+ if (trigger_engine)
+ mtk_cqdma_start_tran(pc, cvd->pd_list[cvd->pd_list_ptr]);
+
+ return 0;
+}
+
+static void mtk_cqdma_issue_vchan_pending(struct mtk_cqdma_device *cqdma,
+ struct mtk_cqdma_vchan *cvc)
+{
+ struct virt_dma_desc *vd, *vd2;
+ int err;
+
+ lockdep_assert_held(&cvc->vc.lock);
+
+ list_for_each_entry_safe(vd, vd2, &cvc->vc.desc_issued, node) {
+ struct mtk_cqdma_vdesc *cvd;
+
+ cvd = to_cqdma_vdesc(vd);
+
+ /* issue VD to PC's queue */
+ err = mtk_cqdma_issue_pending_vdesc(cqdma, cvc->pc, cvd);
+
+ if (err == -ENOSPC)
+ break;
+
+ /* remove VD from list desc_issued */
+ list_del(&vd->node);
+ }
+}
+
+/*
+ * return true if this VC is active,
+ * meaning that there are VDs under processing by the PC
+ */
+static bool mtk_cqdma_is_vchan_active(struct mtk_cqdma_vchan *cvc)
+{
+ struct mtk_cqdma_vdesc *cvd;
+
+ list_for_each_entry(cvd, &cvc->pc->queue, node)
+ if (cvc == to_cqdma_vchan(cvd->ch))
+ return true;
+
+ return false;
+}
+
+static void mtk_cqdma_consume_work_queue(struct mtk_cqdma_pchan *pc)
+{
+ struct mtk_cqdma_vchan *cvc;
+ struct mtk_cqdma_vdesc *cvd;
+
+ /* consume a VD from queue */
+ cvd = list_first_entry_or_null(&pc->queue,
+ struct mtk_cqdma_vdesc, node);
+ if (unlikely(!cvd))
+ return;
+
+ /* update residue of VD */
+ cvd->residue -= cvd->pd_list[cvd->pd_list_ptr]->len;
+
+ cvc = to_cqdma_vchan(cvd->ch);
+
+ if (cvd->pd_list_ptr == cvd->pd_list_len - 1) {
+ /* delete VD from queue if its PD list completed */
+ list_del(&cvd->node);
+
+ spin_lock(&cvc->vc.lock);
+
+ /* add VD into list desc_completed */
+ vchan_cookie_complete(&cvd->vd);
+
+ /* setup completion if this VC is under synchronization */
+ if (cvc->issue_synchronize && !mtk_cqdma_is_vchan_active(cvc)) {
+ complete(&cvc->issue_completion);
+ cvc->issue_synchronize = false;
+ }
+
+ spin_unlock(&cvc->vc.lock);
+ } else {
+ /* there are physical descs queueing to be served */
+ cvd->pd_list_ptr++;
+ }
+
+ /* start transaction for next PD if queue is not empty */
+ cvd = list_first_entry_or_null(&pc->queue,
+ struct mtk_cqdma_vdesc, node);
+ if (cvd)
+ mtk_cqdma_start_tran(pc, cvd->pd_list[cvd->pd_list_ptr]);
+}
+
+static irqreturn_t mtk_cqdma_irq(int irq, void *devid)
+{
+ struct mtk_cqdma_device *cqdma = devid;
+ irqreturn_t ret = IRQ_NONE;
+ u32 i;
+
+ /* clear interrupt flags for each PC */
+ for (i = 0; i < cqdma->dma_channels; ++i) {
+ spin_lock(&cqdma->pc[i]->lock);
+ if (mtk_dma_read(cqdma->pc[i],
+ MTK_CQDMA_INT_FLAG) & MTK_CQDMA_INT_FLAG_BIT) {
+ /* clear interrupt */
+ mtk_dma_clr(cqdma->pc[i], MTK_CQDMA_INT_FLAG,
+ MTK_CQDMA_INT_FLAG_BIT);
+
+ /* consume the queue */
+ mtk_cqdma_consume_work_queue(cqdma->pc[i]);
+ ret = IRQ_HANDLED;
+ }
+ spin_unlock(&cqdma->pc[i]->lock);
+ }
+
+ return ret;
+}
+
+static struct virt_dma_desc *mtk_cqdma_find_active_desc(struct dma_chan *c,
+ dma_cookie_t cookie)
+{
+ struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
+ struct virt_dma_desc *vd;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cvc->pc->lock, flags);
+ list_for_each_entry(vd, &cvc->pc->queue, node)
+ if (vd->tx.cookie == cookie) {
+ spin_unlock_irqrestore(&cvc->pc->lock, flags);
+ return vd;
+ }
+ spin_unlock_irqrestore(&cvc->pc->lock, flags);
+
+ list_for_each_entry(vd, &cvc->vc.desc_issued, node)
+ if (vd->tx.cookie == cookie)
+ return vd;
+
+ return NULL;
+}
+
+static enum dma_status mtk_cqdma_tx_status(struct dma_chan *c,
+ dma_cookie_t cookie,
+ struct dma_tx_state *txstate)
+{
+ struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
+ struct mtk_cqdma_vdesc *cvd;
+ struct virt_dma_desc *vd;
+ enum dma_status ret;
+ unsigned long flags;
+ size_t bytes = 0;
+
+ ret = dma_cookie_status(c, cookie, txstate);
+ if (ret == DMA_COMPLETE || !txstate)
+ return ret;
+
+ spin_lock_irqsave(&cvc->vc.lock, flags);
+ vd = mtk_cqdma_find_active_desc(c, cookie);
+ spin_unlock_irqrestore(&cvc->vc.lock, flags);
+
+ if (vd) {
+ cvd = to_cqdma_vdesc(vd);
+ bytes = cvd->residue;
+ }
+
+ dma_set_residue(txstate, bytes);
+
+ return ret;
+}
+
+static void mtk_cqdma_issue_pending(struct dma_chan *c)
+{
+ struct mtk_cqdma_device *cqdma = to_cqdma_dev(c);
+ struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
+ unsigned long pc_flags;
+ unsigned long vc_flags;
+
+ /* acquire PC's lock first due to lock dependency in ISR */
+ spin_lock_irqsave(&cvc->pc->lock, pc_flags);
+ spin_lock_irqsave(&cvc->vc.lock, vc_flags);
+
+ if (vchan_issue_pending(&cvc->vc))
+ mtk_cqdma_issue_vchan_pending(cqdma, cvc);
+
+ spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
+ spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
+}
+
+static struct dma_async_tx_descriptor *
+mtk_cqdma_prep_dma_memcpy(struct dma_chan *c, dma_addr_t dest,
+ dma_addr_t src, size_t len, unsigned long flags)
+{
+ struct mtk_cqdma_vdesc *cvd;
+ size_t pd_list_len, tlen, i;
+
+ cvd = kzalloc(sizeof(*cvd), GFP_NOWAIT);
+ if (!cvd)
+ return NULL;
+
+ /* setup dma channel */
+ cvd->ch = c;
+
+ /* setup sourece, destination, and length */
+ cvd->len = len;
+ cvd->residue = len;
+ cvd->src = src;
+ cvd->dest = dest;
+
+ /* setup PD list */
+ pd_list_len = DIV_ROUND_UP(len, MTK_CQDMA_MAX_LEN);
+ cvd->pd_list_len = pd_list_len;
+ cvd->pd_list_ptr = 0;
+
+ cvd->pd_list = kcalloc(pd_list_len, sizeof(struct mtk_cqdma_pdesc **),
+ GFP_NOWAIT);
+ if (!cvd->pd_list) {
+ kfree(cvd);
+ return NULL;
+ }
+
+ for (i = 0; i < pd_list_len; ++i) {
+ cvd->pd_list[i] = kzalloc(sizeof(struct mtk_cqdma_pdesc *),
+ GFP_NOWAIT);
+ if (!cvd->pd_list[i]) {
+ for (; i > 0; --i)
+ kfree(cvd->pd_list[i - 1]);
+ kfree(cvd->pd_list);
+ kfree(cvd);
+ return NULL;
+ }
+
+ tlen = (len > MTK_CQDMA_MAX_LEN) ? MTK_CQDMA_MAX_LEN : len;
+
+ cvd->pd_list[i]->src = cvd->src + cvd->len - tlen;
+ cvd->pd_list[i]->dest = cvd->dest + cvd->len - tlen;
+ cvd->pd_list[i]->len = tlen;
+ len -= tlen;
+ }
+
+ return vchan_tx_prep(to_virt_chan(c), &cvd->vd, flags);
+}
+
+static void mtk_cqdma_free_inactive_desc(struct dma_chan *c)
+{
+ struct virt_dma_chan *vc = to_virt_chan(c);
+ unsigned long flags;
+ LIST_HEAD(head);
+
+ /*
+ * set desc_allocated, desc_submitted,
+ * and desc_issued as the candicates to be freed
+ */
+ spin_lock_irqsave(&vc->lock, flags);
+ list_splice_tail_init(&vc->desc_allocated, &head);
+ list_splice_tail_init(&vc->desc_submitted, &head);
+ list_splice_tail_init(&vc->desc_issued, &head);
+ spin_unlock_irqrestore(&vc->lock, flags);
+
+ /* free descriptor lists */
+ vchan_dma_desc_free_list(vc, &head);
+}
+
+static void mtk_cqdma_free_active_desc(struct dma_chan *c)
+{
+ struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
+ bool sync_needed = false;
+ unsigned long pc_flags;
+ unsigned long vc_flags;
+
+ /* acquire PC's lock first due to lock dependency in dma ISR */
+ spin_lock_irqsave(&cvc->pc->lock, pc_flags);
+ spin_lock_irqsave(&cvc->vc.lock, vc_flags);
+
+ /* synchronization is required if this VC is active */
+ if (mtk_cqdma_is_vchan_active(cvc)) {
+ cvc->issue_synchronize = true;
+ sync_needed = true;
+ }
+
+ spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
+ spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
+
+ /* waiting for the completion of this VC */
+ if (sync_needed)
+ wait_for_completion(&cvc->issue_completion);
+
+ /* free all descriptors in list desc_completed */
+ vchan_synchronize(&cvc->vc);
+
+ WARN_ONCE(!list_empty(&cvc->vc.desc_completed),
+ "Desc pending still in list desc_completed\n");
+}
+
+static int mtk_cqdma_terminate_all(struct dma_chan *c)
+{
+ /* free descriptors not processed yet by hardware */
+ mtk_cqdma_free_inactive_desc(c);
+
+ /* free descriptors being processed by hardware */
+ mtk_cqdma_free_active_desc(c);
+
+ return 0;
+}
+
+static int mtk_cqdma_alloc_chan_resources(struct dma_chan *c)
+{
+ struct mtk_cqdma_device *cqdma = to_cqdma_dev(c);
+ struct mtk_cqdma_vchan *vc = to_cqdma_vchan(c);
+ struct mtk_cqdma_pchan *pc = NULL;
+ u32 i, min_refcnt = U32_MAX, refcnt;
+ unsigned long flags;
+
+ /* allocate PC with the minimun refcount */
+ for (i = 0; i < cqdma->dma_channels; ++i) {
+ refcnt = refcount_read(&cqdma->pc[i]->refcnt);
+ if (refcnt < min_refcnt) {
+ pc = cqdma->pc[i];
+ min_refcnt = refcnt;
+ }
+ }
+
+ if (!pc)
+ return -ENOSPC;
+
+ spin_lock_irqsave(&pc->lock, flags);
+
+ if (!refcount_read(&pc->refcnt)) {
+ /* allocate PC when the refcount is zero */
+ mtk_cqdma_alloc_pchan(pc);
+ /*
+ * refcount_inc would complain increment on 0; use-after-free.
+ * Thus, we need to explicitly set it as 1 initially.
+ */
+ refcount_set(&pc->refcnt, 1);
+ } else {
+ refcount_inc(&pc->refcnt);
+ }
+
+ spin_unlock_irqrestore(&pc->lock, flags);
+
+ vc->pc = pc;
+
+ return 0;
+}
+
+static void mtk_cqdma_free_chan_resources(struct dma_chan *c)
+{
+ struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
+ unsigned long flags;
+
+ /* free all descriptors in all lists on the VC */
+ mtk_cqdma_terminate_all(c);
+
+ spin_lock_irqsave(&cvc->pc->lock, flags);
+
+ /* PC is not freed until there is no VC mapped to it */
+ if (refcount_dec_and_test(&cvc->pc->refcnt))
+ mtk_cqdma_free_pchan(cvc->pc);
+
+ spin_unlock_irqrestore(&cvc->pc->lock, flags);
+}
+
+static int mtk_cqdma_hw_init(struct mtk_cqdma_device *cqdma)
+{
+ unsigned long flags;
+ int err;
+ u32 i;
+
+ pm_runtime_enable(cqdma2dev(cqdma));
+ pm_runtime_get_sync(cqdma2dev(cqdma));
+
+ err = clk_prepare_enable(cqdma->clk);
+
+ if (err) {
+ pm_runtime_put_sync(cqdma2dev(cqdma));
+ pm_runtime_disable(cqdma2dev(cqdma));
+ return err;
+ }
+
+ /* reset all PCs */
+ for (i = 0; i < cqdma->dma_channels; ++i) {
+ spin_lock_irqsave(&cqdma->pc[i]->lock, flags);
+ if (mtk_cqdma_hard_reset(cqdma->pc[i]) < 0) {
+ pr_warn("cqdma hard reset timeout\n");
+ spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
+
+ clk_disable_unprepare(cqdma->clk);
+ pm_runtime_put_sync(cqdma2dev(cqdma));
+ pm_runtime_disable(cqdma2dev(cqdma));
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
+ }
+
+ return 0;
+}
+
+static void mtk_cqdma_hw_deinit(struct mtk_cqdma_device *cqdma)
+{
+ unsigned long flags;
+ u32 i;
+
+ /* reset all PCs */
+ for (i = 0; i < cqdma->dma_channels; ++i) {
+ spin_lock_irqsave(&cqdma->pc[i]->lock, flags);
+ if (mtk_cqdma_hard_reset(cqdma->pc[i]) < 0)
+ pr_warn("cqdma hard reset timeout\n");
+ spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
+ }
+
+ clk_disable_unprepare(cqdma->clk);
+
+ pm_runtime_put_sync(cqdma2dev(cqdma));
+ pm_runtime_disable(cqdma2dev(cqdma));
+}
+
+static const struct of_device_id mtk_cqdma_match[] = {
+ { .compatible = "mediatek,mt6765-cqdma" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_cqdma_match);
+
+static int mtk_cqdma_probe(struct platform_device *pdev)
+{
+ struct mtk_cqdma_device *cqdma;
+ struct mtk_cqdma_vchan *vc;
+ struct dma_device *dd;
+ struct resource *res;
+ int err;
+ u32 i;
+
+ cqdma = devm_kzalloc(&pdev->dev, sizeof(*cqdma), GFP_KERNEL);
+ if (!cqdma)
+ return -ENOMEM;
+
+ dd = &cqdma->ddev;
+
+ cqdma->clk = devm_clk_get(&pdev->dev, "cqdma");
+ if (IS_ERR(cqdma->clk)) {
+ dev_err(&pdev->dev, "No clock for %s\n",
+ dev_name(&pdev->dev));
+ return PTR_ERR(cqdma->clk);
+ }
+
+ dma_cap_set(DMA_MEMCPY, dd->cap_mask);
+
+ dd->copy_align = MTK_CQDMA_ALIGN_SIZE;
+ dd->device_alloc_chan_resources = mtk_cqdma_alloc_chan_resources;
+ dd->device_free_chan_resources = mtk_cqdma_free_chan_resources;
+ dd->device_tx_status = mtk_cqdma_tx_status;
+ dd->device_issue_pending = mtk_cqdma_issue_pending;
+ dd->device_prep_dma_memcpy = mtk_cqdma_prep_dma_memcpy;
+ dd->device_terminate_all = mtk_cqdma_terminate_all;
+ dd->src_addr_widths = MTK_CQDMA_DMA_BUSWIDTHS;
+ dd->dst_addr_widths = MTK_CQDMA_DMA_BUSWIDTHS;
+ dd->directions = BIT(DMA_MEM_TO_MEM);
+ dd->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
+ dd->dev = &pdev->dev;
+ INIT_LIST_HEAD(&dd->channels);
+
+ if (pdev->dev.of_node && of_property_read_u32(pdev->dev.of_node,
+ "dma-requests",
+ &cqdma->dma_requests)) {
+ dev_info(&pdev->dev,
+ "Using %u as missing dma-requests property\n",
+ MTK_CQDMA_NR_VCHANS);
+
+ cqdma->dma_requests = MTK_CQDMA_NR_VCHANS;
+ }
+
+ if (pdev->dev.of_node && of_property_read_u32(pdev->dev.of_node,
+ "dma-channels",
+ &cqdma->dma_channels)) {
+ dev_info(&pdev->dev,
+ "Using %u as missing dma-channels property\n",
+ MTK_CQDMA_NR_PCHANS);
+
+ cqdma->dma_channels = MTK_CQDMA_NR_PCHANS;
+ }
+
+ cqdma->pc = devm_kcalloc(&pdev->dev, cqdma->dma_channels,
+ sizeof(*cqdma->pc), GFP_KERNEL);
+ if (!cqdma->pc)
+ return -ENOMEM;
+
+ /* initialization for PCs */
+ for (i = 0; i < cqdma->dma_channels; ++i) {
+ cqdma->pc[i] = devm_kcalloc(&pdev->dev, 1,
+ sizeof(**cqdma->pc), GFP_KERNEL);
+ if (!cqdma->pc[i])
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&cqdma->pc[i]->queue);
+ spin_lock_init(&cqdma->pc[i]->lock);
+ refcount_set(&cqdma->pc[i]->refcnt, 0);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (!res) {
+ dev_err(&pdev->dev, "No mem resource for %s\n",
+ dev_name(&pdev->dev));
+ return -EINVAL;
+ }
+
+ cqdma->pc[i]->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(cqdma->pc[i]->base))
+ return PTR_ERR(cqdma->pc[i]->base);
+
+ /* allocate IRQ resource */
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+ if (!res) {
+ dev_err(&pdev->dev, "No irq resource for %s\n",
+ dev_name(&pdev->dev));
+ return -EINVAL;
+ }
+ cqdma->pc[i]->irq = res->start;
+
+ err = devm_request_irq(&pdev->dev, cqdma->pc[i]->irq,
+ mtk_cqdma_irq, 0, dev_name(&pdev->dev),
+ cqdma);
+ if (err) {
+ dev_err(&pdev->dev,
+ "request_irq failed with err %d\n", err);
+ return -EINVAL;
+ }
+ }
+
+ /* allocate resource for VCs */
+ cqdma->vc = devm_kcalloc(&pdev->dev, cqdma->dma_requests,
+ sizeof(*cqdma->vc), GFP_KERNEL);
+ if (!cqdma->vc)
+ return -ENOMEM;
+
+ for (i = 0; i < cqdma->dma_requests; i++) {
+ vc = &cqdma->vc[i];
+ vc->vc.desc_free = mtk_cqdma_vdesc_free;
+ vchan_init(&vc->vc, dd);
+ init_completion(&vc->issue_completion);
+ }
+
+ err = dma_async_device_register(dd);
+ if (err)
+ return err;
+
+ err = of_dma_controller_register(pdev->dev.of_node,
+ of_dma_xlate_by_chan_id, cqdma);
+ if (err) {
+ dev_err(&pdev->dev,
+ "MediaTek CQDMA OF registration failed %d\n", err);
+ goto err_unregister;
+ }
+
+ err = mtk_cqdma_hw_init(cqdma);
+ if (err) {
+ dev_err(&pdev->dev,
+ "MediaTek CQDMA HW initialization failed %d\n", err);
+ goto err_unregister;
+ }
+
+ platform_set_drvdata(pdev, cqdma);
+
+ dev_info(&pdev->dev, "MediaTek CQDMA driver registered\n");
+
+ return 0;
+
+err_unregister:
+ dma_async_device_unregister(dd);
+
+ return err;
+}
+
+static int mtk_cqdma_remove(struct platform_device *pdev)
+{
+ struct mtk_cqdma_device *cqdma = platform_get_drvdata(pdev);
+ struct mtk_cqdma_vchan *vc;
+ unsigned long flags;
+ int i;
+
+ /* kill VC task */
+ for (i = 0; i < cqdma->dma_requests; i++) {
+ vc = &cqdma->vc[i];
+
+ list_del(&vc->vc.chan.device_node);
+ tasklet_kill(&vc->vc.task);
+ }
+
+ /* disable interrupt */
+ for (i = 0; i < cqdma->dma_channels; i++) {
+ spin_lock_irqsave(&cqdma->pc[i]->lock, flags);
+ mtk_dma_clr(cqdma->pc[i], MTK_CQDMA_INT_EN,
+ MTK_CQDMA_INT_EN_BIT);
+ spin_unlock_irqrestore(&cqdma->pc[i]->lock, flags);
+
+ /* Waits for any pending IRQ handlers to complete */
+ synchronize_irq(cqdma->pc[i]->irq);
+ }
+
+ /* disable hardware */
+ mtk_cqdma_hw_deinit(cqdma);
+
+ dma_async_device_unregister(&cqdma->ddev);
+ of_dma_controller_free(pdev->dev.of_node);
+
+ return 0;
+}
+
+static struct platform_driver mtk_cqdma_driver = {
+ .probe = mtk_cqdma_probe,
+ .remove = mtk_cqdma_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = mtk_cqdma_match,
+ },
+};
+module_platform_driver(mtk_cqdma_driver);
+
+MODULE_DESCRIPTION("MediaTek CQDMA Controller Driver");
+MODULE_AUTHOR("Shun-Chih Yu <shun-chih.yu@mediatek.com>");
+MODULE_LICENSE("GPL v2");
^ permalink raw reply related
* [1/2] dt-bindings: dmaengine: Add MediaTek Command-Queue DMA controller bindings
From: shun-chih.yu @ 2018-09-04 8:43 UTC (permalink / raw)
To: Sean Wang, Vinod Koul, Rob Herring, Matthias Brugger,
Dan Williams
Cc: dmaengine, linux-arm-kernel, linux-mediatek, devicetree,
linux-kernel, srv_wsdupstream, Shun-Chih Yu
From: Shun-Chih Yu <shun-chih.yu@mediatek.com>
Document the devicetree bindings for MediaTek Command-Queue DMA controller
which could be found on MT6765 SoC or other similar Mediatek SoCs.
Signed-off-by: Shun-Chih Yu <shun-chih.yu@mediatek.com>
---
.../devicetree/bindings/dma/mtk-cqdma.txt | 31 ++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dma/mtk-cqdma.txt
diff --git a/Documentation/devicetree/bindings/dma/mtk-cqdma.txt b/Documentation/devicetree/bindings/dma/mtk-cqdma.txt
new file mode 100644
index 0000000..fb12927
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/mtk-cqdma.txt
@@ -0,0 +1,31 @@
+MediaTek Command-Queue DMA Controller
+==================================
+
+Required properties:
+
+- compatible: Must be "mediatek,mt6765-cqdma" for MT6765.
+- reg: Should contain the base address and length for each channel.
+- interrupts: Should contain references to the interrupts for each channel.
+- clocks: Should be the clock specifiers corresponding to the entry in
+ clock-names property.
+- clock-names: Should contain "cqdma" entries.
+- dma-channels: The number of DMA channels supported by the controller.
+- dma-requests: The number of DMA request supported by the controller.
+- #dma-cells: The length of the DMA specifier, must be <1>. This one cell
+ in dmas property of a client device represents the channel
+ number.
+Example:
+
+ cqdma: dma-controller@10212000 {
+ compatible = "mediatek,mt6765-cqdma";
+ reg = <0 0x10212000 0 0x1000>;
+ interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_SPI 114 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&infracfg CLK_IFR_CQ_DMA>;
+ clock-names = "cqdma";
+ dma-channels = <2>;
+ dma-requests = <32>;
+ #dma-cells = <1>;
+ };
+
+DMA clients must use the format described in dma/dma.txt file.
^ permalink raw reply related
* [3/4] dmaengine: imx-sdma: implement channel termination via worker
From: Robin Gong @ 2018-09-04 2:36 UTC (permalink / raw)
To: Lucas Stach, Vinod Koul
Cc: dmaengine@vger.kernel.org, dl-linux-imx, kernel@pengutronix.de,
patchwork-lst@pengutronix.de
> -----Original Message-----
> From: Lucas Stach <l.stach@pengutronix.de>
> Sent: 2018年9月3日 21:12
> To: Robin Gong <yibin.gong@nxp.com>; Vinod Koul <vkoul@kernel.org>
> Cc: dmaengine@vger.kernel.org; dl-linux-imx <linux-imx@nxp.com>;
> kernel@pengutronix.de; patchwork-lst@pengutronix.de
> Subject: Re: [PATCH 3/4] dmaengine: imx-sdma: implement channel
> termination via worker
>
> Am Montag, den 03.09.2018, 08:59 +0000 schrieb Robin Gong:
> > > -----Original Message-----
> > > From: Lucas Stach <l.stach@pengutronix.de>
> > > Sent: 2018年9月3日 16:41
> > > To: Robin Gong <yibin.gong@nxp.com>; Vinod Koul <vkoul@kernel.org>
> > > Cc: dmaengine@vger.kernel.org; dl-linux-imx <linux-imx@nxp.com>;
> > > kernel@pengutronix.de; patchwork-lst@pengutronix.de
> > > Subject: Re: [PATCH 3/4] dmaengine: imx-sdma: implement channel
> > > termination via worker
> > >
> > > Hi Robin,
> > >
> > > Am Freitag, den 31.08.2018, 09:49 +0000 schrieb Robin Gong:
> > > > Hi Lucas,
> > > > Seems I miss your previous mail. Thanks for your patch, but if
> > > > move most jobs of sdma_disable_channel_with_delay() into worker,
> > > > that will bring another race condition that upper driver such as
> > > > Audio terminate channel and free resource of dma channel without
> > > > really channel stop, if dma transfer done interrupt come after
> > > > that, oops or kernel cash may be caught. Leave 'sdmac->desc =
> > > > NULL' in the
> > >
> > > sdma_disable_channel_with_delay() may fix such potential issue.
> > >
> > > No, there is no such issue. The audio channel terminate will call
> > > dmaengine_terminate_sync(), which internally calls
> > > dmaengine_terminate_async() and then does a dmaengine_synchronize().
> > > As this patchset implements the device_synchronize function in the
> > > sdma driver, this will wait for the worker to finish its execution,
> > > so there is no race condition to worry about here.
> > >
> > > Regards,
> > > Lucas
> >
> > Yes, but how about other drivers which not call
> > dmaengine_terminate_sync()?
>
> Please read the dmaengine documentation. device_terminate_all has no
> requirement that the transfer is actually canceled when the call returns. If the
> caller needs a guarantee that the channel is stopped it _must_ call
> device_synchronize.
I know that, but the fact is some driver still use dmaengine_terminate_all() such as
Spi/uart driver. My concern is how to avoid to break their function.
>
> For your convenience I'm copying the relevant part of the docs below (from
> dmaengine_terminate_async(), which is what calls
> device_terminate_all():
>
> "Calling this function will terminate all active and pending descriptors that have
> previously been submitted to the channel. It is not guaranteed though that the
> transfer for the active descriptor has stopped when the function returns.
> Furthermore it is possible the complete callback of a submitted transfer is still
> running when this function returns.
>
> dmaengine_synchronize() needs to be called before it is safe to free any
> memory that is accessed by previously submitted descriptors or before freeing
> any resources accessed from within the completion callback of any previously
> submitted descriptors."
>
> Regards,
> Lucas
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox