DMA Engine development
 help / color / mirror / Atom feed
* [v4,1/5] dmaengine: imx-sdma: add clock ratio 1:1 check
From: Lucas Stach @ 2019-01-25  9:42 UTC (permalink / raw)
  To: Angus Ainslie (Purism)
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Daniel Baluta

Am Donnerstag, den 24.01.2019, 19:55 -0700 schrieb Angus Ainslie (Purism):
> On i.mx8 mscale B0 chip, AHB/SDMA clock ratio 2:1 can't be supportted,
> since SDMA clock ratio has to be increased to 250Mhz, AHB can't reach
> to 500Mhz, so use 1:1 instead.
> 
> > Based on NXP commit MLK-16841-1 by Robin Gong <yibin.gong@nxp.com>
> 
> > Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
> ---
>  drivers/dma/imx-sdma.c | 21 +++++++++++++++++----
>  1 file changed, 17 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
> index 0b3a67ff8e82..5e5ef0b5a973 100644
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -440,6 +440,8 @@ struct sdma_engine {
> > >  	unsigned int			irq;
> > >  	dma_addr_t			bd0_phys;
> > >  	struct sdma_buffer_descriptor	*bd0;
> > +	/* clock ratio for AHB:SDMA core. 1:1 is 1, 2:1 is 0*/
> > > +	bool				clk_ratio;
>  };
>  
>  static int sdma_config_write(struct dma_chan *chan,
> @@ -662,8 +664,14 @@ static int sdma_run_channel0(struct sdma_engine *sdma)
> >  		dev_err(sdma->dev, "Timeout waiting for CH0 ready\n");
>  
> >  	/* Set bits of CONFIG register with dynamic context switching */
> > -	if (readl(sdma->regs + SDMA_H_CONFIG) == 0)
> > -		writel_relaxed(SDMA_H_CONFIG_CSM, sdma->regs + SDMA_H_CONFIG);
> +	if ((readl(sdma->regs + SDMA_H_CONFIG) & ~SDMA_H_CONFIG_ACR) == 0) {

The intention of this code was probably to set the CSM bit if they
weren't set before, so masking out individual bits from the register
and risking to skip this when one of the other bits was set doesn't
seem right.

I guess the whole block can be simplified to:

reg = readl(sdma->regs + SDMA_H_CONFIG);
if ((reg & SDMA_H_CONFIG_CSM) != SDMA_H_CONFIG_CSM)
	reg |= SDMA_H_CONFIG_CSM;
writel_relaxed(reg, sdma->regs + SDMA_H_CONFIG);

Regards,
Lucas

> +		reg = SDMA_H_CONFIG_CSM;
> +
> > +		if (sdma->clk_ratio)
> > +			reg |= SDMA_H_CONFIG_ACR;
> +
> > +		writel_relaxed(reg, sdma->regs + SDMA_H_CONFIG);
> > +	}
>  
> >  	return ret;
>  }
> @@ -1840,6 +1848,9 @@ static int sdma_init(struct sdma_engine *sdma)
> >  	if (ret)
> >  		goto disable_clk_ipg;
>  
> > +	if (clk_get_rate(sdma->clk_ahb) == clk_get_rate(sdma->clk_ipg))
> > +		sdma->clk_ratio = 1;
> +
> >  	/* Be sure SDMA has not started yet */
> >  	writel_relaxed(0, sdma->regs + SDMA_H_C0PTR);
>  
> @@ -1880,8 +1891,10 @@ static int sdma_init(struct sdma_engine *sdma)
> >  	writel_relaxed(0x4050, sdma->regs + SDMA_CHN0ADDR);
>  
> >  	/* Set bits of CONFIG register but with static context switching */
> > -	/* FIXME: Check whether to set ACR bit depending on clock ratios */
> > -	writel_relaxed(0, sdma->regs + SDMA_H_CONFIG);
> > +	if (sdma->clk_ratio)
> > +		writel_relaxed(SDMA_H_CONFIG_ACR, sdma->regs + SDMA_H_CONFIG);
> > +	else
> > +		writel_relaxed(0, sdma->regs + SDMA_H_CONFIG);
>  
> >  	writel_relaxed(ccb_phys, sdma->regs + SDMA_H_C0PTR);
>

^ permalink raw reply

* [v4,3/5] dt-bindings: dma: fsl-imx-sdma: add fsl,imx8mq to the accepted compatible node
From: Lucas Stach @ 2019-01-25  9:39 UTC (permalink / raw)
  To: Angus Ainslie (Purism)
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Daniel Baluta

Am Donnerstag, den 24.01.2019, 19:55 -0700 schrieb Angus Ainslie (Purism):
> The imx8mq is a slightly different variant on the imx7d

Not really AFAICS, but it's good to document the used compatibles
anyways, so:

Reviewed-by: Lucas Stach <l.stach@pengutronix.de>

> Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
> ---
>  Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt b/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
> index 3c9a57a8443b..9d8bbac27d8b 100644
> --- a/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
> +++ b/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
> @@ -9,6 +9,7 @@ Required properties:
>        "fsl,imx53-sdma"
>        "fsl,imx6q-sdma"
>        "fsl,imx7d-sdma"
> +      "fsl,imx8mq-sdma"
>    The -to variants should be preferred since they allow to determine the
>    correct ROM script addresses needed for the driver to work without additional
>    firmware.

^ permalink raw reply

* [v4,5/5] imx8mq.dtsi: add the sdma nodes
From: Lucas Stach @ 2019-01-25  9:38 UTC (permalink / raw)
  To: Angus Ainslie (Purism)
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Daniel Baluta

Am Donnerstag, den 24.01.2019, 19:55 -0700 schrieb Angus Ainslie (Purism):
> Add the sdma nodes to the base devicetree for the imx8mq
> 
> Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>

One nit below, with that fixed:

Reviewed-by: Lucas Stach <l.stach@pengutronix.de>

You might need to split this patch out from the series and send it to
Shawn separately after the dmaengine changes have been accepted.

regards,
Lucas

> ---
>  arch/arm64/boot/dts/freescale/imx8mq.dtsi | 28 +++++++++++++++++++++++
>  1 file changed, 28 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> index c0402375e7c1..f0cd3675ead0 100644
> --- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> +++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> @@ -336,6 +336,17 @@
> >  				clocks = <&clk IMX8MQ_CLK_WDOG3_ROOT>;
> >  				status = "disabled";
> >  			};
> +
> > > +			sdma2: sdma@302c0000 {
> > +				compatible = "fsl,imx8mq-sdma", "fsl,imx7d-sdma";
> > +				reg = <0x302c0000 0x10000>;
> > +				interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
> > +				clocks = <&clk IMX8MQ_CLK_SDMA2_ROOT>,
> +					<&clk IMX8MQ_CLK_SDMA2_ROOT>;

Some spaces to align the second clock reference as in the rest of this
file, please.

> +				clock-names = "ipg", "ahb";
> > +				#dma-cells = <3>;
> > +				fsl,sdma-ram-script-name = "imx/sdma/sdma-imx7d.bin";
> > +			};
> >  		};
>  
> >  		bus@30400000 { /* AIPS2 */
> @@ -370,6 +381,8 @@
> >  				clocks = <&clk IMX8MQ_CLK_UART3_ROOT>,
> >  				         <&clk IMX8MQ_CLK_UART3_ROOT>;
> >  				clock-names = "ipg", "per";
> > +				dmas = <&sdma1 26 4 0>, <&sdma1 27 4 0>;
> > +				dma-names = "rx", "tx";
> >  				status = "disabled";
> >  			};
>  
> @@ -381,6 +394,8 @@
> >  				clocks = <&clk IMX8MQ_CLK_UART2_ROOT>,
> >  				         <&clk IMX8MQ_CLK_UART2_ROOT>;
> >  				clock-names = "ipg", "per";
> > +				dmas = <&sdma1 24 4 0>, <&sdma1 25 4 0>;
> > +				dma-names = "rx", "tx";
> >  				status = "disabled";
> >  			};
>  
> @@ -432,6 +447,8 @@
> >  				clocks = <&clk IMX8MQ_CLK_UART4_ROOT>,
> >  				         <&clk IMX8MQ_CLK_UART4_ROOT>;
> >  				clock-names = "ipg", "per";
> > +				dmas = <&sdma1 28 4 0>, <&sdma1 29 4 0>;
> > +				dma-names = "rx", "tx";
> >  				status = "disabled";
> >  			};
>  
> @@ -465,6 +482,17 @@
> >  				status = "disabled";
> >  			};
>  
> > > +			sdma1: sdma@30bd0000 {
> > +				compatible = "fsl,imx8mq-sdma", "fsl,imx7d-sdma";
> > +				reg = <0x30bd0000 0x10000>;
> > +				interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
> > +				clocks = <&clk IMX8MQ_CLK_SDMA1_ROOT>,
> > +					<&clk IMX8MQ_CLK_AHB>;
> > +				clock-names = "ipg", "ahb";
> > +				#dma-cells = <3>;
> > +				fsl,sdma-ram-script-name = "imx/sdma/sdma-imx7d.bin";
> > +			};
> +
> > >  			fec1: ethernet@30be0000 {
> >  				compatible = "fsl,imx8mq-fec", "fsl,imx6sx-fec";
> >  				reg = <0x30be0000 0x10000>;

^ permalink raw reply

* [v4,2/5] dmaengine: imx-sdma: add imx8mq sdma compatible parts
From: Lucas Stach @ 2019-01-25  9:35 UTC (permalink / raw)
  To: Angus Ainslie (Purism)
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Daniel Baluta

Am Donnerstag, den 24.01.2019, 19:55 -0700 schrieb Angus Ainslie (Purism):
> This is identical to the imx7d data structures but we need to
> be able to differentiate that the imx8mq has 2 sdma controllers.

No, we don't, see comment on patch 4/5. This can reuse the imx7d data.

Regards,
Lucas

> Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
> ---
>  drivers/dma/imx-sdma.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
> index 5e5ef0b5a973..ec6fb48f387a 100644
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -556,6 +556,12 @@ static struct sdma_driver_data sdma_imx7d = {
> >  	.script_addrs = &sdma_script_imx7d,
>  };
>  
> +static struct sdma_driver_data sdma_imx8mq = {
> > +	.chnenbl0 = SDMA_CHNENBL0_IMX35,
> > +	.num_events = 48,
> > +	.script_addrs = &sdma_script_imx7d,
> +};
> +
>  static const struct platform_device_id sdma_devtypes[] = {
> >  	{
> >  		.name = "imx25-sdma",
> @@ -578,6 +584,9 @@ static const struct platform_device_id sdma_devtypes[] = {
> >  	}, {
> >  		.name = "imx7d-sdma",
> >  		.driver_data = (unsigned long)&sdma_imx7d,
> > +	}, {
> > +		.name = "imx8mq-sdma",
> > +		.driver_data = (unsigned long)&sdma_imx8mq,
> >  	}, {
> >  		/* sentinel */
> >  	}
> @@ -592,6 +601,7 @@ static const struct of_device_id sdma_dt_ids[] = {
> >  	{ .compatible = "fsl,imx31-sdma", .data = &sdma_imx31, },
> >  	{ .compatible = "fsl,imx25-sdma", .data = &sdma_imx25, },
> >  	{ .compatible = "fsl,imx7d-sdma", .data = &sdma_imx7d, },
> > +	{ .compatible = "fsl,imx8mq-sdma", .data = &sdma_imx8mq, },
> >  	{ /* sentinel */ }
>  };
>  MODULE_DEVICE_TABLE(of, sdma_dt_ids);

^ permalink raw reply

* [v4,4/5] dma: imx-sdma: add a test for imx8mq multi sdma devices
From: Lucas Stach @ 2019-01-25  9:34 UTC (permalink / raw)
  To: Angus Ainslie (Purism)
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Daniel Baluta

Am Donnerstag, den 24.01.2019, 19:55 -0700 schrieb Angus Ainslie (Purism):
> On i.mx8mq, there are two sdma instances, and the common dma framework
> will get a channel dynamically from any available sdma instance whether
> it's the first sdma device or the second sdma device. Some IPs like
> SAI only work with sdma2 not sdma1. To make sure the sdma channel is from
> the correct sdma device, use the phandle to match.
> 
> > Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
> ---
>  drivers/dma/imx-sdma.c                | 8 ++++++++
>  include/linux/platform_data/dma-imx.h | 1 +
>  2 files changed, 9 insertions(+)
> 
> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
> index ec6fb48f387a..88e112a4aabf 100644
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -1927,11 +1927,17 @@ static int sdma_init(struct sdma_engine *sdma)
>  static bool sdma_filter_fn(struct dma_chan *chan, void *fn_param)
>  {
> >  	struct sdma_channel *sdmac = to_sdma_chan(chan);
> > +	struct sdma_engine *sdma = sdmac->sdma;
> >  	struct imx_dma_data *data = fn_param;
>  
> >  	if (!imx_dma_is_general_purpose(chan))
> >  		return false;
>  
> > +	/* return false if it's not the right device */
> +	if ((sdmac->sdma->drvdata == &sdma_imx8mq)

Why do this check only on i.MX8M? This is completely generic and can
and should be done for every SDMA engine with a OF node.

Also why use the phandle to match this instead of the of_node pointer
directly?

Regards,
Lucas

> +		&& (sdma->dev->of_node->phandle != data->phandle))
> > +		return false;
> +
> >  	sdmac->data = *data;
> >  	chan->private = &sdmac->data;
>  
> @@ -1942,6 +1948,7 @@ static struct dma_chan *sdma_xlate(struct of_phandle_args *dma_spec,
> >  				   struct of_dma *ofdma)
>  {
> >  	struct sdma_engine *sdma = ofdma->of_dma_data;
> > +	struct device_node *np = ofdma->of_node;
> >  	dma_cap_mask_t mask = sdma->dma_device.cap_mask;
> >  	struct imx_dma_data data;
>  
> @@ -1959,6 +1966,7 @@ static struct dma_chan *sdma_xlate(struct of_phandle_args *dma_spec,
> >  	 * be set to sdmac->event_id1.
> >  	 */
> >  	data.dma_request2 = 0;
> > +	data.phandle = np->phandle;
>  
> >  	return dma_request_channel(mask, sdma_filter_fn, &data);
>  }
> diff --git a/include/linux/platform_data/dma-imx.h b/include/linux/platform_data/dma-imx.h
> index 7d964e787299..eeb5b73ae542 100644
> --- a/include/linux/platform_data/dma-imx.h
> +++ b/include/linux/platform_data/dma-imx.h
> @@ -55,6 +55,7 @@ struct imx_dma_data {
> >  	int dma_request2; /* secondary DMA request line */
> >  	enum sdma_peripheral_type peripheral_type;
> >  	int priority;
> > +	int phandle;
>  };
>  
>  static inline int imx_dma_is_ipu(struct dma_chan *chan)

^ permalink raw reply

* dmaengine: fsldma: Add 64-bit I/O accessors for powerpc64
From: Peng Ma @ 2019-01-25  5:54 UTC (permalink / raw)
  To: Vinod Koul
  Cc: Scott Wood, Leo Li, Zhang Wei, linuxppc-dev@lists.ozlabs.org,
	dmaengine@vger.kernel.org, Wen He

Hi Vinod,

Sorry to replay late.
1:This patch has already send to the patchwork.
	Please see the patch link: https://patchwork.kernel.org/patch/10741521/
2:I have already compile the fsl patches on arm and powerpc after patched https://patchwork.kernel.org/patch/10741521/
	The compile will successful, please let me know the reported regression results, thanks very much.

Best Regards,
Peng

>-----Original Message-----
>From: Vinod Koul <vkoul@kernel.org>
>Sent: 2019年1月19日 20:59
>To: Peng Ma <peng.ma@nxp.com>
>Cc: Scott Wood <oss@buserror.net>; Leo Li <leoyang.li@nxp.com>; Zhang Wei
><zw@zh-kernel.org>; linuxppc-dev@lists.ozlabs.org;
>dmaengine@vger.kernel.org; Wen He <wen.he_1@nxp.com>
>Subject: Re: [PATCH] dmaengine: fsldma: Add 64-bit I/O accessors for
>powerpc64
>
>On 24-12-18, 05:29, Peng Ma wrote:
>> Hi Scott,
>>
>> Oh, I did not see the in_XX64/out_XX64 supported only __powerpc64__ just
>now.
>> Thanks for your reminder.
>
>Can you send the formal patch for this...
>
>FWIW, fsl patches were not merged last cycle because of reported regression...
>
>>
>> #ifdef __powerpc64__
>>
>> #ifdef __BIG_ENDIAN__
>> DEF_MMIO_OUT_D(out_be64, 64, std);
>> DEF_MMIO_IN_D(in_be64, 64, ld);
>>
>> /* There is no asm instructions for 64 bits reverse loads and stores
>> */ static inline u64 in_le64(const volatile u64 __iomem *addr) {
>>         return swab64(in_be64(addr));
>> }
>>
>> static inline void out_le64(volatile u64 __iomem *addr, u64 val) {
>>         out_be64(addr, swab64(val));
>> }
>> #else
>> DEF_MMIO_OUT_D(out_le64, 64, std);
>> DEF_MMIO_IN_D(in_le64, 64, ld);
>>
>> /* There is no asm instructions for 64 bits reverse loads and stores
>> */ static inline u64 in_be64(const volatile u64 __iomem *addr) {
>>         return swab64(in_le64(addr));
>> }
>>
>> static inline void out_be64(volatile u64 __iomem *addr, u64 val) {
>>         out_le64(addr, swab64(val));
>> }
>>
>> #endif
>> #endif /* __powerpc64__ */
>>
>> Best Regards,
>> Peng
>> >-----Original Message-----
>> >From: Scott Wood <oss@buserror.net>
>> >Sent: 2018年12月24日 12:46
>> >To: Peng Ma <peng.ma@nxp.com>; Leo Li <leoyang.li@nxp.com>; Zhang
>Wei
>> ><zw@zh-kernel.org>
>> >Cc: linuxppc-dev@lists.ozlabs.org; dmaengine@vger.kernel.org; Wen He
>> ><wen.he_1@nxp.com>
>> >Subject: Re: [PATCH] dmaengine: fsldma: Add 64-bit I/O accessors for
>> >powerpc64
>> >
>> >On Mon, 2018-12-24 at 03:42 +0000, Peng Ma wrote:
>> >> Hi Scott,
>> >>
>> >> You are right, we should support powerpc64, so could I changed it
>> >> as
>> >> fallows:
>> >>
>> >> diff --git a/drivers/dma/fsldma.h b/drivers/dma/fsldma.h index
>> >> 88db939..057babf 100644
>> >> --- a/drivers/dma/fsldma.h
>> >> +++ b/drivers/dma/fsldma.h
>> >> @@ -202,35 +202,10 @@ struct fsldma_chan {
>> >>  #define fsl_iowrite32(v, p)    out_le32(p, v)
>> >>  #define fsl_iowrite32be(v, p)  out_be32(p, v)
>> >>
>> >> -#ifndef __powerpc64__
>> >> -static u64 fsl_ioread64(const u64 __iomem *addr) -{
>> >> -       u32 fsl_addr = lower_32_bits(addr);
>> >> -       u64 fsl_addr_hi = (u64)in_le32((u32 *)(fsl_addr + 1)) << 32;
>> >> -
>> >> -       return fsl_addr_hi | in_le32((u32 *)fsl_addr);
>> >> -}
>> >> -
>> >> -static void fsl_iowrite64(u64 val, u64 __iomem *addr) -{
>> >> -       out_le32((u32 __iomem *)addr + 1, val >> 32);
>> >> -       out_le32((u32 __iomem *)addr, (u32)val);
>> >> -}
>> >> -
>> >> -static u64 fsl_ioread64be(const u64 __iomem *addr) -{
>> >> -       u32 fsl_addr = lower_32_bits(addr);
>> >> -       u64 fsl_addr_hi = (u64)in_be32((u32 *)fsl_addr) << 32;
>> >> -
>> >> -       return fsl_addr_hi | in_be32((u32 *)(fsl_addr + 1));
>> >> -}
>> >> -
>> >> -static void fsl_iowrite64be(u64 val, u64 __iomem *addr) -{
>> >> -       out_be32((u32 __iomem *)addr, val >> 32);
>> >> -       out_be32((u32 __iomem *)addr + 1, (u32)val);
>> >> -}
>> >> -#endif
>> >> +#define fsl_ioread64(p)                in_le64(p)
>> >> +#define fsl_ioread64be(p)      in_be64(p)
>> >> +#define fsl_iowrite64(v, p)    out_le64(p, v)
>> >> +#define fsl_iowrite64be(v, p)  out_be64(p, v)
>> >>  #endif
>> >
>> >Then you'll break 32-bit, assuming those
>> >fake-it-with-two-32-bit-accesses were actually needed.
>> >
>> >-Scott
>> >
>>
>
>--
>~Vinod

^ permalink raw reply

* [2/2] dmaengine: mediatek-cqdma: remove redundant queue structure
From: Sean Wang @ 2019-01-25  4:19 UTC (permalink / raw)
  To: shun-chih.yu
  Cc: Sean Wang, Vinod Koul, Rob Herring, Matthias Brugger,
	Dan Williams, devicetree, linux-kernel, srv_wsdupstream,
	linux-mediatek, dmaengine, linux-arm-kernel

On Thu, Jan 24, 2019 at 2:46 AM <shun-chih.yu@mediatek.com> wrote:
>
> From: Shun-Chih Yu <shun-chih.yu@mediatek.com>
>
> This patch introduces active_vdec to indicate the virtual descriptor
> under processing by the CQDMA dmaengine, and simplify the control logic
> by removing redundant queue structure, tasklets, and completion
> management.
>
> Also, wrong residue assignment in mtk_cqdma_tx_status and typos are
> fixed.

overall changes are good and let code become clear, but it's better to
do one thing at a patch instead of mixing all stuff together.

>
> Signed-off-by: Shun-Chih Yu <shun-chih.yu@mediatek.com>
> ---
>  drivers/dma/mediatek/mtk-cqdma.c |  399 ++++++++++----------------------------
>  1 file changed, 98 insertions(+), 301 deletions(-)
>
> diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
> index 131f397..387781b 100644
> --- a/drivers/dma/mediatek/mtk-cqdma.c
> +++ b/drivers/dma/mediatek/mtk-cqdma.c
> @@ -22,6 +22,7 @@
>  #include <linux/of_dma.h>
>  #include <linux/platform_device.h>
>  #include <linux/pm_runtime.h>
> +#include <linux/preempt.h>
>  #include <linux/refcount.h>
>  #include <linux/slab.h>
>
> @@ -47,7 +48,6 @@
>  #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
>
> @@ -69,45 +69,32 @@
>   *                         descriptor (CVD)
>   * @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
> - * @node:                  The lise_head struct to build link-list for VDs
> - * @parent:                The pointer to the parent CVD
>   */
>  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;
> -
> -       struct list_head node;
> -       struct mtk_cqdma_vdesc *parent;
>  };
>
>  /**
>   * struct mtk_cqdma_pchan - The struct holding info describing physical
>   *                         channel (PC)
> - * @queue:                 Queue for the PDs issued to this PC
> + * @active_vdesc:          The pointer to the CVD which is under processing
>   * @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
> - * @tasklet:               Tasklet for this PC
>   * @lock:                  Lock protect agaisting multiple VCs access PC
>   */
>  struct mtk_cqdma_pchan {
> -       struct list_head queue;
> +       struct mtk_cqdma_vdesc *active_vdesc;
>         void __iomem *base;
>         u32 irq;
> -
>         refcount_t refcnt;
> -
> -       struct tasklet_struct tasklet;
> -
> -       /* lock to protect PC */
>         spinlock_t lock;
>  };
>
> @@ -116,14 +103,10 @@ struct mtk_cqdma_pchan {
>   *                         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;
>  };
>
>  /**
> @@ -168,7 +151,7 @@ static struct device *cqdma2dev(struct mtk_cqdma_device *cqdma)
>
>  static u32 mtk_dma_read(struct mtk_cqdma_pchan *pc, u32 reg)
>  {
> -       return readl(pc->base + reg);
> +       return readl_relaxed(pc->base + reg);

that can be moved to separate patch and explains why

>  }
>
>  static void mtk_dma_write(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
> @@ -202,22 +185,22 @@ static void mtk_cqdma_vdesc_free(struct virt_dma_desc *vd)
>         kfree(to_cqdma_vdesc(vd));
>  }
>
> -static int mtk_cqdma_poll_engine_done(struct mtk_cqdma_pchan *pc, bool atomic)
> +static int mtk_cqdma_poll_engine_done(struct mtk_cqdma_pchan *pc)
>  {
>         u32 status = 0;
>
> -       if (!atomic)
> +       if (!in_task())

if (in_task())

>                 return readl_poll_timeout(pc->base + MTK_CQDMA_EN,
>                                           status,
>                                           !(status & MTK_CQDMA_EN_BIT),
>                                           MTK_CQDMA_USEC_POLL,
>                                           MTK_CQDMA_TIMEOUT_POLL);
> -
> -       return readl_poll_timeout_atomic(pc->base + MTK_CQDMA_EN,
> -                                        status,
> -                                        !(status & MTK_CQDMA_EN_BIT),
> -                                        MTK_CQDMA_USEC_POLL,
> -                                        MTK_CQDMA_TIMEOUT_POLL);
> +       else
> +               return readl_poll_timeout_atomic(pc->base + MTK_CQDMA_EN,
> +                                                status,
> +                                                !(status & MTK_CQDMA_EN_BIT),
> +                                                MTK_CQDMA_USEC_POLL,
> +                                                MTK_CQDMA_TIMEOUT_POLL);
>  }
>
>  static int mtk_cqdma_hard_reset(struct mtk_cqdma_pchan *pc)
> @@ -225,20 +208,17 @@ 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, false);
> +       return mtk_cqdma_poll_engine_done(pc);
>  }
>
>  static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc,
>                             struct mtk_cqdma_vdesc *cvd)
>  {
> -       /* wait for the previous transaction done */
> -       if (mtk_cqdma_poll_engine_done(pc, true) < 0)
> -               dev_err(cqdma2dev(to_cqdma_dev(cvd->ch)), "cqdma wait transaction timeout\n");
> -
>         /* warm reset the dma engine for the new transaction */
>         mtk_dma_set(pc, MTK_CQDMA_RESET, MTK_CQDMA_WARM_RST_BIT);
> -       if (mtk_cqdma_poll_engine_done(pc, true) < 0)
> -               dev_err(cqdma2dev(to_cqdma_dev(cvd->ch)), "cqdma warm reset timeout\n");
> +       if (mtk_cqdma_poll_engine_done(pc) < 0)
> +               dev_err(cqdma2dev(to_cqdma_dev(cvd->ch)),
> +                       "cqdma warm reset timeout\n");
>
>         /* setup the source */
>         mtk_dma_set(pc, MTK_CQDMA_SRC, cvd->src & MTK_CQDMA_ADDR_LIMIT);
> @@ -253,11 +233,12 @@ static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc,
>  #ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
>         mtk_dma_set(pc, MTK_CQDMA_DST2, cvd->dest >> MTK_CQDMA_ADDR2_SHFIT);
>  #else
> -       mtk_dma_set(pc, MTK_CQDMA_SRC2, 0);
> +       mtk_dma_set(pc, MTK_CQDMA_DST2, 0);

if it is mistakenly coded, it should be moved to separate fixup patch
with a tag  Fixes: b1f01e48df5a ("dmaengine: mediatek: Add MediaTek
Command-Queue DMA controller for MT6765 SoC")

>  #endif
>
>         /* setup the length */
> -       mtk_dma_set(pc, MTK_CQDMA_LEN1, cvd->len);
> +       mtk_dma_set(pc, MTK_CQDMA_LEN1, (cvd->len < MTK_CQDMA_MAX_LEN) ?
> +                   cvd->len : MTK_CQDMA_MAX_LEN);
>
>         /* start dma engine */
>         mtk_dma_set(pc, MTK_CQDMA_EN, MTK_CQDMA_EN_BIT);
> @@ -265,30 +246,17 @@ static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc,
>
>  static void mtk_cqdma_issue_vchan_pending(struct mtk_cqdma_vchan *cvc)
>  {
> -       struct virt_dma_desc *vd, *vd2;
> +       struct virt_dma_desc *vd;
>         struct mtk_cqdma_pchan *pc = cvc->pc;
> -       struct mtk_cqdma_vdesc *cvd;
> -       bool trigger_engine = false;
>
>         lockdep_assert_held(&cvc->vc.lock);
>         lockdep_assert_held(&pc->lock);
>
> -       list_for_each_entry_safe(vd, vd2, &cvc->vc.desc_issued, node) {
> -               /* need to trigger dma engine if PC's queue is empty */
> -               if (list_empty(&pc->queue))
> -                       trigger_engine = true;
> -
> -               cvd = to_cqdma_vdesc(vd);
> +       vd = vchan_next_desc(&cvc->vc);
>
> -               /* add VD into PC's queue */
> -               list_add_tail(&cvd->node, &pc->queue);
> -
> -               /* start the dma engine */
> -               if (trigger_engine)
> -                       mtk_cqdma_start(pc, cvd);
> -
> -               /* remove VD from list desc_issued */
> -               list_del(&vd->node);
> +       if (vd && !pc->active_vdesc) {
> +               pc->active_vdesc = to_cqdma_vdesc(vd);
> +               mtk_cqdma_start(pc, pc->active_vdesc);
>         }
>  }
>
> @@ -298,100 +266,55 @@ static void mtk_cqdma_issue_vchan_pending(struct mtk_cqdma_vchan *cvc)
>   */
>  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;
> +       return (!cvc->pc->active_vdesc) ? false :
> +              (cvc == to_cqdma_vchan(cvc->pc->active_vdesc->ch));
>  }
>
> -/*
> - * return the pointer of the CVD that is just consumed by the PC
> - */
> -static struct mtk_cqdma_vdesc
> -*mtk_cqdma_consume_work_queue(struct mtk_cqdma_pchan *pc)
> +static void mtk_cqdma_complete_vdesc(struct mtk_cqdma_pchan *pc)
>  {
>         struct mtk_cqdma_vchan *cvc;
> -       struct mtk_cqdma_vdesc *cvd, *ret = NULL;
> -
> -       /* consume a CVD from PC's queue */
> -       cvd = list_first_entry_or_null(&pc->queue,
> -                                      struct mtk_cqdma_vdesc, node);
> -       if (unlikely(!cvd || !cvd->parent))
> -               return NULL;
> +       struct mtk_cqdma_vdesc *cvd;
> +       struct virt_dma_desc *vd;
> +       size_t tlen;
>
> +       cvd = pc->active_vdesc;
>         cvc = to_cqdma_vchan(cvd->ch);
> -       ret = cvd;
> -
> -       /* update residue of the parent CVD */
> -       cvd->parent->residue -= cvd->len;
>
> -       /* delete CVD from PC's queue */
> -       list_del(&cvd->node);
> +       tlen = (cvd->len < MTK_CQDMA_MAX_LEN) ? cvd->len : MTK_CQDMA_MAX_LEN;
> +       cvd->len -= tlen;
> +       cvd->src += tlen;
> +       cvd->dest += tlen;
>
>         spin_lock(&cvc->vc.lock);
>
> -       /* check whether all the child CVDs completed */
> -       if (!cvd->parent->residue) {
> -               /* add the parent VD into list desc_completed */
> -               vchan_cookie_complete(&cvd->parent->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);
> -
> -       /* start transaction for next CVD in the queue */
> -       cvd = list_first_entry_or_null(&pc->queue,
> -                                      struct mtk_cqdma_vdesc, node);
> -       if (cvd)
> -               mtk_cqdma_start(pc, cvd);
> +       /* check whether the VD completed */
> +       if (!cvd->len) {
> +               /* delete VD from desc_issued */
> +               list_del(&cvd->vd.node);
>
> -       return ret;
> -}
> -
> -static void mtk_cqdma_tasklet_cb(unsigned long data)
> -{
> -       struct mtk_cqdma_pchan *pc = (struct mtk_cqdma_pchan *)data;
> -       struct mtk_cqdma_vdesc *cvd = NULL;
> -       unsigned long flags;
> +               /* add the VD into list desc_completed */
> +               vchan_cookie_complete(&cvd->vd);
>
> -       spin_lock_irqsave(&pc->lock, flags);
> -       /* consume the queue */
> -       cvd = mtk_cqdma_consume_work_queue(pc);
> -       spin_unlock_irqrestore(&pc->lock, flags);
> -
> -       /* submit the next CVD */
> -       if (cvd) {
> -               dma_run_dependencies(&cvd->vd.tx);
> -
> -               /*
> -                * free child CVD after completion.
> -                * the parent CVD would be freeed with desc_free by user.
> -                */
> -               if (cvd->parent != cvd)
> -                       kfree(cvd);
> +               /* get the next active VD */
> +               vd = vchan_next_desc(&cvc->vc);
> +               pc->active_vdesc = (!vd) ? NULL : to_cqdma_vdesc(vd);
>         }
>
> -       /* re-enable interrupt before leaving tasklet */
> -       enable_irq(pc->irq);
> +       /* start the next transaction */
> +       if (pc->active_vdesc)
> +               mtk_cqdma_start(pc, pc->active_vdesc);
> +
> +       spin_unlock(&cvc->vc.lock);
>  }
>
>  static irqreturn_t mtk_cqdma_irq(int irq, void *devid)
>  {
>         struct mtk_cqdma_device *cqdma = devid;
>         irqreturn_t ret = IRQ_NONE;
> -       bool schedule_tasklet = false;
>         u32 i;
>
>         /* clear interrupt flags for each PC */
> -       for (i = 0; i < cqdma->dma_channels; ++i, schedule_tasklet = false) {
> +       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) {
> @@ -399,72 +322,21 @@ static irqreturn_t mtk_cqdma_irq(int irq, void *devid)
>                         mtk_dma_clr(cqdma->pc[i], MTK_CQDMA_INT_FLAG,
>                                     MTK_CQDMA_INT_FLAG_BIT);
>
> -                       schedule_tasklet = true;
> +                       mtk_cqdma_complete_vdesc(cqdma->pc[i]);
> +
>                         ret = IRQ_HANDLED;
>                 }
>                 spin_unlock(&cqdma->pc[i]->lock);
> -
> -               if (schedule_tasklet) {
> -                       /* disable interrupt */
> -                       disable_irq_nosync(cqdma->pc[i]->irq);
> -
> -                       /* schedule the tasklet to handle the transactions */
> -                       tasklet_schedule(&cqdma->pc[i]->tasklet);
> -               }
>         }
>
>         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;
> +       return dma_cookie_status(c, cookie, txstate);
>  }
>
>  static void mtk_cqdma_issue_pending(struct dma_chan *c)
> @@ -473,7 +345,7 @@ static void mtk_cqdma_issue_pending(struct dma_chan *c)
>         unsigned long pc_flags;
>         unsigned long vc_flags;
>
> -       /* acquire PC's lock before VS's lock for lock dependency in tasklet */
> +       /* acquire PC's lock before VC's lock for lock dependency in ISR */
>         spin_lock_irqsave(&cvc->pc->lock, pc_flags);
>         spin_lock_irqsave(&cvc->vc.lock, vc_flags);
>
> @@ -488,124 +360,56 @@ static void mtk_cqdma_issue_pending(struct dma_chan *c)
>  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;
> -       struct dma_async_tx_descriptor *tx = NULL, *prev_tx = NULL;
> -       size_t i, tlen, nr_vd;
> -
> -       /*
> -        * In the case that trsanction length is larger than the
> -        * DMA engine supports, a single memcpy transaction needs
> -        * to be separated into several DMA transactions.
> -        * Each DMA transaction would be described by a CVD,
> -        * and the first one is referred as the parent CVD,
> -        * while the others are child CVDs.
> -        * The parent CVD's tx descriptor is the only tx descriptor
> -        * returned to the DMA user, and it should not be completed
> -        * until all the child CVDs completed.
> -        */
> -       nr_vd = DIV_ROUND_UP(len, MTK_CQDMA_MAX_LEN);
> -       cvd = kcalloc(nr_vd, sizeof(*cvd), GFP_NOWAIT);
> +       struct mtk_cqdma_vdesc *cvd;
> +
> +       cvd = kzalloc(sizeof(*cvd), GFP_NOWAIT);
>         if (!cvd)
>                 return NULL;
>
> -       for (i = 0; i < nr_vd; ++i) {
> -               cvd[i] = kzalloc(sizeof(*cvd[i]), GFP_NOWAIT);
> -               if (!cvd[i]) {
> -                       for (; i > 0; --i)
> -                               kfree(cvd[i - 1]);
> -                       return NULL;
> -               }
> +       /* setup dma channel */
> +       cvd->ch = c;
>
> -               /* setup dma channel */
> -               cvd[i]->ch = c;
> +       /* setup sourece, destination, and length */
> +       cvd->len = len;
> +       cvd->src = src;
> +       cvd->dest = dest;
>
> -               /* setup sourece, destination, and length */
> -               tlen = (len > MTK_CQDMA_MAX_LEN) ? MTK_CQDMA_MAX_LEN : len;
> -               cvd[i]->len = tlen;
> -               cvd[i]->src = src;
> -               cvd[i]->dest = dest;
> -
> -               /* setup tx descriptor */
> -               tx = vchan_tx_prep(to_virt_chan(c), &cvd[i]->vd, flags);
> -               tx->next = NULL;
> -
> -               if (!i) {
> -                       cvd[0]->residue = len;
> -               } else {
> -                       prev_tx->next = tx;
> -                       cvd[i]->residue = tlen;
> -               }
> -
> -               cvd[i]->parent = cvd[0];
> -
> -               /* update the src, dest, len, prev_tx for the next CVD */
> -               src += tlen;
> -               dest += tlen;
> -               len -= tlen;
> -               prev_tx = tx;
> -       }
> -
> -       return &cvd[0]->vd.tx;
> +       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)
> +static int mtk_cqdma_terminate_all(struct dma_chan *c)
>  {
>         struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
> -       bool sync_needed = false;
> +       struct virt_dma_chan *vc = to_virt_chan(c);
>         unsigned long pc_flags;
>         unsigned long vc_flags;
> +       LIST_HEAD(head);
>
> -       /* 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;
> -       }
> +       do {
> +               /* 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);
>
> -       spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
> -       spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
> +               /* wait for the VC to be inactive  */
> +               if (mtk_cqdma_is_vchan_active(cvc)) {
> +                       spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
> +                       spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
> +                       continue;
> +               }
>
> -       /* waiting for the completion of this VC */
> -       if (sync_needed)
> -               wait_for_completion(&cvc->issue_completion);
> +               /* get VDs from lists */
> +               vchan_get_all_descriptors(vc, &head);
>
> -       /* free all descriptors in list desc_completed */
> -       vchan_synchronize(&cvc->vc);
> +               /* free all the VDs */
> +               vchan_dma_desc_free_list(vc, &head);
>
> -       WARN_ONCE(!list_empty(&cvc->vc.desc_completed),
> -                 "Desc pending still in list desc_completed\n");
> -}
> +               spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
> +               spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
>
> -static int mtk_cqdma_terminate_all(struct dma_chan *c)
> -{
> -       /* free descriptors not processed yet by hardware */
> -       mtk_cqdma_free_inactive_desc(c);
> +               break;
> +       } while (1);

use wait_for_completion_timeout or something like that instead of
polling here to reduce cpu computing waste and to avoid dead-loop risk

>
> -       /* free descriptors being processed by hardware */
> -       mtk_cqdma_free_active_desc(c);
> +       vchan_synchronize(&cvc->vc);
>
>         return 0;
>  }
> @@ -618,7 +422,7 @@ static int mtk_cqdma_alloc_chan_resources(struct dma_chan *c)
>         u32 i, min_refcnt = U32_MAX, refcnt;
>         unsigned long flags;
>
> -       /* allocate PC with the minimun refcount */
> +       /* allocate PC with the minimum refcount */
>         for (i = 0; i < cqdma->dma_channels; ++i) {
>                 refcnt = refcount_read(&cqdma->pc[i]->refcnt);
>                 if (refcnt < min_refcnt) {
> @@ -671,8 +475,9 @@ static void mtk_cqdma_free_chan_resources(struct dma_chan *c)
>                 mtk_dma_set(cvc->pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
>
>                 /* wait for the completion of flush operation */
> -               if (mtk_cqdma_poll_engine_done(cvc->pc, false) < 0)
> -                       dev_err(cqdma2dev(to_cqdma_dev(c)), "cqdma flush timeout\n");
> +               if (mtk_cqdma_poll_engine_done(cvc->pc) < 0)
> +                       dev_err(cqdma2dev(to_cqdma_dev(c)),
> +                               "cqdma flush timeout\n");
>
>                 /* clear the flush bit and interrupt flag */
>                 mtk_dma_clr(cvc->pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
> @@ -787,9 +592,9 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
>         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);
> +               dev_dbg(&pdev->dev,
> +                       "Using %u as missing dma-requests property\n",
> +                       MTK_CQDMA_NR_VCHANS);
>
>                 cqdma->dma_requests = MTK_CQDMA_NR_VCHANS;
>         }
> @@ -797,9 +602,9 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
>         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);
> +               dev_dbg(&pdev->dev,
> +                       "Using %u as missing dma-channels property\n",
> +                       MTK_CQDMA_NR_PCHANS);
>
>                 cqdma->dma_channels = MTK_CQDMA_NR_PCHANS;
>         }
> @@ -816,7 +621,7 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
>                 if (!cqdma->pc[i])
>                         return -ENOMEM;
>
> -               INIT_LIST_HEAD(&cqdma->pc[i]->queue);
> +               cqdma->pc[i]->active_vdesc = NULL;
>                 spin_lock_init(&cqdma->pc[i]->lock);
>                 refcount_set(&cqdma->pc[i]->refcnt, 0);
>
> @@ -860,7 +665,6 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
>                 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);
> @@ -884,12 +688,7 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
>
>         platform_set_drvdata(pdev, cqdma);
>
> -       /* initialize tasklet for each PC */
> -       for (i = 0; i < cqdma->dma_channels; ++i)
> -               tasklet_init(&cqdma->pc[i]->tasklet, mtk_cqdma_tasklet_cb,
> -                            (unsigned long)cqdma->pc[i]);
> -
> -       dev_info(&pdev->dev, "MediaTek CQDMA driver registered\n");
> +       dev_dbg(&pdev->dev, "MediaTek CQDMA driver registered\n");
>
>         return 0;
>
> @@ -923,8 +722,6 @@ static int mtk_cqdma_remove(struct platform_device *pdev)
>
>                 /* Waits for any pending IRQ handlers to complete */
>                 synchronize_irq(cqdma->pc[i]->irq);
> -
> -               tasklet_kill(&cqdma->pc[i]->tasklet);
>         }
>
>         /* disable hardware */
> --
> 1.7.9.5
>
>
> _______________________________________________
> Linux-mediatek mailing list
> Linux-mediatek@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-mediatek

^ permalink raw reply

* [1/2] dt-bindings: dmaengine: Add MediaTek Command-Queue DMA controller bindings
From: Sean Wang @ 2019-01-25  3:52 UTC (permalink / raw)
  To: shun-chih.yu
  Cc: Sean Wang, Vinod Koul, Rob Herring, Matthias Brugger,
	Dan Williams, devicetree, linux-kernel, srv_wsdupstream,
	linux-mediatek, dmaengine, linux-arm-kernel

You dropped the version tag that doesn't reflect that is a newer one
and that would be easily ignored by other people

On Thu, Jan 24, 2019 at 2:46 AM <shun-chih.yu@mediatek.com> wrote:
>
> 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>
> Reviewed-by: Rob Herring <robh@kernel.org>

otherwise, Acked-by: Sean Wang <sean.wang@kernel.org>

> ---
>  .../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.
> --
> 1.7.9.5
>
>
> _______________________________________________
> Linux-mediatek mailing list
> Linux-mediatek@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-mediatek

^ permalink raw reply

* [v4,5/5] imx8mq.dtsi: add the sdma nodes
From: Angus Ainslie @ 2019-01-25  2:55 UTC (permalink / raw)
  To: angus
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Lucas Stach, Daniel Baluta

Add the sdma nodes to the base devicetree for the imx8mq

Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
---
 arch/arm64/boot/dts/freescale/imx8mq.dtsi | 28 +++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
index c0402375e7c1..f0cd3675ead0 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
@@ -336,6 +336,17 @@
 				clocks = <&clk IMX8MQ_CLK_WDOG3_ROOT>;
 				status = "disabled";
 			};
+
+			sdma2: sdma@302c0000 {
+				compatible = "fsl,imx8mq-sdma", "fsl,imx7d-sdma";
+				reg = <0x302c0000 0x10000>;
+				interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&clk IMX8MQ_CLK_SDMA2_ROOT>,
+					<&clk IMX8MQ_CLK_SDMA2_ROOT>;
+				clock-names = "ipg", "ahb";
+				#dma-cells = <3>;
+				fsl,sdma-ram-script-name = "imx/sdma/sdma-imx7d.bin";
+			};
 		};
 
 		bus@30400000 { /* AIPS2 */
@@ -370,6 +381,8 @@
 				clocks = <&clk IMX8MQ_CLK_UART3_ROOT>,
 				         <&clk IMX8MQ_CLK_UART3_ROOT>;
 				clock-names = "ipg", "per";
+				dmas = <&sdma1 26 4 0>, <&sdma1 27 4 0>;
+				dma-names = "rx", "tx";
 				status = "disabled";
 			};
 
@@ -381,6 +394,8 @@
 				clocks = <&clk IMX8MQ_CLK_UART2_ROOT>,
 				         <&clk IMX8MQ_CLK_UART2_ROOT>;
 				clock-names = "ipg", "per";
+				dmas = <&sdma1 24 4 0>, <&sdma1 25 4 0>;
+				dma-names = "rx", "tx";
 				status = "disabled";
 			};
 
@@ -432,6 +447,8 @@
 				clocks = <&clk IMX8MQ_CLK_UART4_ROOT>,
 				         <&clk IMX8MQ_CLK_UART4_ROOT>;
 				clock-names = "ipg", "per";
+				dmas = <&sdma1 28 4 0>, <&sdma1 29 4 0>;
+				dma-names = "rx", "tx";
 				status = "disabled";
 			};
 
@@ -465,6 +482,17 @@
 				status = "disabled";
 			};
 
+			sdma1: sdma@30bd0000 {
+				compatible = "fsl,imx8mq-sdma", "fsl,imx7d-sdma";
+				reg = <0x30bd0000 0x10000>;
+				interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&clk IMX8MQ_CLK_SDMA1_ROOT>,
+					<&clk IMX8MQ_CLK_AHB>;
+				clock-names = "ipg", "ahb";
+				#dma-cells = <3>;
+				fsl,sdma-ram-script-name = "imx/sdma/sdma-imx7d.bin";
+			};
+
 			fec1: ethernet@30be0000 {
 				compatible = "fsl,imx8mq-fec", "fsl,imx6sx-fec";
 				reg = <0x30be0000 0x10000>;

^ permalink raw reply related

* [v4,4/5] dma: imx-sdma: add a test for imx8mq multi sdma devices
From: Angus Ainslie @ 2019-01-25  2:55 UTC (permalink / raw)
  To: angus
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Lucas Stach, Daniel Baluta

On i.mx8mq, there are two sdma instances, and the common dma framework
will get a channel dynamically from any available sdma instance whether
it's the first sdma device or the second sdma device. Some IPs like
SAI only work with sdma2 not sdma1. To make sure the sdma channel is from
the correct sdma device, use the phandle to match.

Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
---
 drivers/dma/imx-sdma.c                | 8 ++++++++
 include/linux/platform_data/dma-imx.h | 1 +
 2 files changed, 9 insertions(+)

diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index ec6fb48f387a..88e112a4aabf 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -1927,11 +1927,17 @@ static int sdma_init(struct sdma_engine *sdma)
 static bool sdma_filter_fn(struct dma_chan *chan, void *fn_param)
 {
 	struct sdma_channel *sdmac = to_sdma_chan(chan);
+	struct sdma_engine *sdma = sdmac->sdma;
 	struct imx_dma_data *data = fn_param;
 
 	if (!imx_dma_is_general_purpose(chan))
 		return false;
 
+	/* return false if it's not the right device */
+	if ((sdmac->sdma->drvdata == &sdma_imx8mq)
+		&& (sdma->dev->of_node->phandle != data->phandle))
+		return false;
+
 	sdmac->data = *data;
 	chan->private = &sdmac->data;
 
@@ -1942,6 +1948,7 @@ static struct dma_chan *sdma_xlate(struct of_phandle_args *dma_spec,
 				   struct of_dma *ofdma)
 {
 	struct sdma_engine *sdma = ofdma->of_dma_data;
+	struct device_node *np = ofdma->of_node;
 	dma_cap_mask_t mask = sdma->dma_device.cap_mask;
 	struct imx_dma_data data;
 
@@ -1959,6 +1966,7 @@ static struct dma_chan *sdma_xlate(struct of_phandle_args *dma_spec,
 	 * be set to sdmac->event_id1.
 	 */
 	data.dma_request2 = 0;
+	data.phandle = np->phandle;
 
 	return dma_request_channel(mask, sdma_filter_fn, &data);
 }
diff --git a/include/linux/platform_data/dma-imx.h b/include/linux/platform_data/dma-imx.h
index 7d964e787299..eeb5b73ae542 100644
--- a/include/linux/platform_data/dma-imx.h
+++ b/include/linux/platform_data/dma-imx.h
@@ -55,6 +55,7 @@ struct imx_dma_data {
 	int dma_request2; /* secondary DMA request line */
 	enum sdma_peripheral_type peripheral_type;
 	int priority;
+	int phandle;
 };
 
 static inline int imx_dma_is_ipu(struct dma_chan *chan)

^ permalink raw reply related

* [v4,3/5] dt-bindings: dma: fsl-imx-sdma: add fsl,imx8mq to the accepted compatible node
From: Angus Ainslie @ 2019-01-25  2:55 UTC (permalink / raw)
  To: angus
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Lucas Stach, Daniel Baluta

The imx8mq is a slightly different variant on the imx7d

Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
---
 Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt b/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
index 3c9a57a8443b..9d8bbac27d8b 100644
--- a/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
+++ b/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
@@ -9,6 +9,7 @@ Required properties:
       "fsl,imx53-sdma"
       "fsl,imx6q-sdma"
       "fsl,imx7d-sdma"
+      "fsl,imx8mq-sdma"
   The -to variants should be preferred since they allow to determine the
   correct ROM script addresses needed for the driver to work without additional
   firmware.

^ permalink raw reply related

* [v4,2/5] dmaengine: imx-sdma: add imx8mq sdma compatible parts
From: Angus Ainslie @ 2019-01-25  2:55 UTC (permalink / raw)
  To: angus
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Lucas Stach, Daniel Baluta

This is identical to the imx7d data structures but we need to
be able to differentiate that the imx8mq has 2 sdma controllers.

Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
---
 drivers/dma/imx-sdma.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index 5e5ef0b5a973..ec6fb48f387a 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -556,6 +556,12 @@ static struct sdma_driver_data sdma_imx7d = {
 	.script_addrs = &sdma_script_imx7d,
 };
 
+static struct sdma_driver_data sdma_imx8mq = {
+	.chnenbl0 = SDMA_CHNENBL0_IMX35,
+	.num_events = 48,
+	.script_addrs = &sdma_script_imx7d,
+};
+
 static const struct platform_device_id sdma_devtypes[] = {
 	{
 		.name = "imx25-sdma",
@@ -578,6 +584,9 @@ static const struct platform_device_id sdma_devtypes[] = {
 	}, {
 		.name = "imx7d-sdma",
 		.driver_data = (unsigned long)&sdma_imx7d,
+	}, {
+		.name = "imx8mq-sdma",
+		.driver_data = (unsigned long)&sdma_imx8mq,
 	}, {
 		/* sentinel */
 	}
@@ -592,6 +601,7 @@ static const struct of_device_id sdma_dt_ids[] = {
 	{ .compatible = "fsl,imx31-sdma", .data = &sdma_imx31, },
 	{ .compatible = "fsl,imx25-sdma", .data = &sdma_imx25, },
 	{ .compatible = "fsl,imx7d-sdma", .data = &sdma_imx7d, },
+	{ .compatible = "fsl,imx8mq-sdma", .data = &sdma_imx8mq, },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, sdma_dt_ids);

^ permalink raw reply related

* [v4,1/5] dmaengine: imx-sdma: add clock ratio 1:1 check
From: Angus Ainslie @ 2019-01-25  2:55 UTC (permalink / raw)
  To: angus
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Lucas Stach, Daniel Baluta

On i.mx8 mscale B0 chip, AHB/SDMA clock ratio 2:1 can't be supportted,
since SDMA clock ratio has to be increased to 250Mhz, AHB can't reach
to 500Mhz, so use 1:1 instead.

Based on NXP commit MLK-16841-1 by Robin Gong <yibin.gong@nxp.com>

Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
---
 drivers/dma/imx-sdma.c | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index 0b3a67ff8e82..5e5ef0b5a973 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -440,6 +440,8 @@ struct sdma_engine {
 	unsigned int			irq;
 	dma_addr_t			bd0_phys;
 	struct sdma_buffer_descriptor	*bd0;
+	/* clock ratio for AHB:SDMA core. 1:1 is 1, 2:1 is 0*/
+	bool				clk_ratio;
 };
 
 static int sdma_config_write(struct dma_chan *chan,
@@ -662,8 +664,14 @@ static int sdma_run_channel0(struct sdma_engine *sdma)
 		dev_err(sdma->dev, "Timeout waiting for CH0 ready\n");
 
 	/* Set bits of CONFIG register with dynamic context switching */
-	if (readl(sdma->regs + SDMA_H_CONFIG) == 0)
-		writel_relaxed(SDMA_H_CONFIG_CSM, sdma->regs + SDMA_H_CONFIG);
+	if ((readl(sdma->regs + SDMA_H_CONFIG) & ~SDMA_H_CONFIG_ACR) == 0) {
+		reg = SDMA_H_CONFIG_CSM;
+
+		if (sdma->clk_ratio)
+			reg |= SDMA_H_CONFIG_ACR;
+
+		writel_relaxed(reg, sdma->regs + SDMA_H_CONFIG);
+	}
 
 	return ret;
 }
@@ -1840,6 +1848,9 @@ static int sdma_init(struct sdma_engine *sdma)
 	if (ret)
 		goto disable_clk_ipg;
 
+	if (clk_get_rate(sdma->clk_ahb) == clk_get_rate(sdma->clk_ipg))
+		sdma->clk_ratio = 1;
+
 	/* Be sure SDMA has not started yet */
 	writel_relaxed(0, sdma->regs + SDMA_H_C0PTR);
 
@@ -1880,8 +1891,10 @@ static int sdma_init(struct sdma_engine *sdma)
 	writel_relaxed(0x4050, sdma->regs + SDMA_CHN0ADDR);
 
 	/* Set bits of CONFIG register but with static context switching */
-	/* FIXME: Check whether to set ACR bit depending on clock ratios */
-	writel_relaxed(0, sdma->regs + SDMA_H_CONFIG);
+	if (sdma->clk_ratio)
+		writel_relaxed(SDMA_H_CONFIG_ACR, sdma->regs + SDMA_H_CONFIG);
+	else
+		writel_relaxed(0, sdma->regs + SDMA_H_CONFIG);
 
 	writel_relaxed(ccb_phys, sdma->regs + SDMA_H_C0PTR);
 

^ permalink raw reply related

* [5/8,v5] dma: k3dma: Add support for dma-channel-mask
From: John Stultz @ 2019-01-24 20:24 UTC (permalink / raw)
  To: lkml
  Cc: Li Yu, Dan Williams, Vinod Koul, Tanglei Han, Zhuangluan Su,
	Ryan Grachek, Manivannan Sadhasivam, Guodong Xu, dmaengine,
	John Stultz

From: Li Yu <liyu65@hisilicon.com>

Add dma-channel-mask as a property for k3dma, it defines
available dma channels which a non-secure mode driver can use.

One sample usage of this is in Hi3660 SoC. DMA channel 0 is
reserved to lpm3, which is a coprocessor for power management. So
as a result, any request in kernel (which runs on main processor
and in non-secure mode) should start from at least channel 1.

Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Tanglei Han <hantanglei@huawei.com>
Cc: Zhuangluan Su <suzhuangluan@hisilicon.com>
Cc: Ryan Grachek <ryan@edited.us>
Cc: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Cc: Guodong Xu <guodong.xu@linaro.org>
Cc: dmaengine@vger.kernel.org
Signed-off-by: Li Yu <liyu65@hisilicon.com>
[jstultz: Reworked to use a channel mask]
Signed-off-by: John Stultz <john.stultz@linaro.org>
---
v3: Rename to hisi-dma-avail-chan
v4: Rename to dma-channel-mask
v5: Use BIT(i) instead of (1<<i)
---
 drivers/dma/k3dma.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/k3dma.c b/drivers/dma/k3dma.c
index e415c85..294ec64 100644
--- a/drivers/dma/k3dma.c
+++ b/drivers/dma/k3dma.c
@@ -111,6 +111,7 @@ struct k3_dma_dev {
 	struct dma_pool		*pool;
 	u32			dma_channels;
 	u32			dma_requests;
+	u32			dma_channel_mask;
 	unsigned int		irq;
 };
 
@@ -319,6 +320,9 @@ static void k3_dma_tasklet(unsigned long arg)
 	/* check new channel request in d->chan_pending */
 	spin_lock_irq(&d->lock);
 	for (pch = 0; pch < d->dma_channels; pch++) {
+		if (!(d->dma_channel_mask & (1<<pch)))
+			continue;
+
 		p = &d->phy[pch];
 
 		if (p->vchan == NULL && !list_empty(&d->chan_pending)) {
@@ -336,6 +340,9 @@ static void k3_dma_tasklet(unsigned long arg)
 	spin_unlock_irq(&d->lock);
 
 	for (pch = 0; pch < d->dma_channels; pch++) {
+		if (!(d->dma_channel_mask & (1<<pch)))
+			continue;
+
 		if (pch_alloc & (1 << pch)) {
 			p = &d->phy[pch];
 			c = p->vchan;
@@ -856,6 +863,13 @@ static int k3_dma_probe(struct platform_device *op)
 				"dma-channels", &d->dma_channels);
 		of_property_read_u32((&op->dev)->of_node,
 				"dma-requests", &d->dma_requests);
+		ret = of_property_read_u32((&op->dev)->of_node,
+				"dma-channel-mask", &d->dma_channel_mask);
+		if (ret) {
+			dev_warn(&op->dev,
+				 "dma-channel-mask doesn't exist, considering all as available.\n");
+			d->dma_channel_mask = (u32)~0UL;
+		}
 	}
 
 	if (!(soc_data->flags & K3_FLAG_NOCLK)) {
@@ -887,8 +901,12 @@ static int k3_dma_probe(struct platform_device *op)
 		return -ENOMEM;
 
 	for (i = 0; i < d->dma_channels; i++) {
-		struct k3_dma_phy *p = &d->phy[i];
+		struct k3_dma_phy *p;
+
+		if (!(d->dma_channel_mask & BIT(i)))
+			continue;
 
+		p = &d->phy[i];
 		p->idx = i;
 		p->base = d->base + i * 0x40;
 	}

^ permalink raw reply related

* [4/8,v5] dma: k3dma: Delete axi_config
From: John Stultz @ 2019-01-24 20:24 UTC (permalink / raw)
  To: lkml
  Cc: Li Yu, Dan Williams, Vinod Koul, Tanglei Han, Zhuangluan Su,
	Ryan Grachek, Manivannan Sadhasivam, dmaengine, Guodong Xu,
	John Stultz

From: Li Yu <liyu65@hisilicon.com>

Axi_config controls whether DMA resources can be accessed in non-secure
mode, such as linux kernel. The register should be set by the bootloader
stage and depends on the device.

Thus, this patch removes axi_config from k3dma driver.

Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Tanglei Han <hantanglei@huawei.com>
Cc: Zhuangluan Su <suzhuangluan@hisilicon.com>
Cc: Ryan Grachek <ryan@edited.us>
Cc: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Cc: dmaengine@vger.kernel.org
Acked-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Signed-off-by: Li Yu <liyu65@hisilicon.com>
Signed-off-by: Guodong Xu <guodong.xu@linaro.org>
[jstultz: Minor tweaks to commit message]
Signed-off-by: John Stultz <john.stultz@linaro.org>
---
 drivers/dma/k3dma.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/drivers/dma/k3dma.c b/drivers/dma/k3dma.c
index 4dce532..e415c85 100644
--- a/drivers/dma/k3dma.c
+++ b/drivers/dma/k3dma.c
@@ -52,8 +52,6 @@
 #define CX_SRC			0x814
 #define CX_DST			0x818
 #define CX_CFG			0x81c
-#define AXI_CFG			0x820
-#define AXI_CFG_DEFAULT		0x201201
 
 #define CX_LLI_CHAIN_EN		0x2
 #define CX_CFG_EN		0x1
@@ -169,7 +167,6 @@ static void k3_dma_set_desc(struct k3_dma_phy *phy, struct k3_desc_hw *hw)
 	writel_relaxed(hw->count, phy->base + CX_CNT0);
 	writel_relaxed(hw->saddr, phy->base + CX_SRC);
 	writel_relaxed(hw->daddr, phy->base + CX_DST);
-	writel_relaxed(AXI_CFG_DEFAULT, phy->base + AXI_CFG);
 	writel_relaxed(hw->config, phy->base + CX_CFG);
 }
 

^ permalink raw reply related

* [3/8,v5] dma: k3dma: Upgrade k3dma driver to support hisi_asp_dma hardware
From: John Stultz @ 2019-01-24 20:24 UTC (permalink / raw)
  To: lkml
  Cc: Youlin Wang, Dan Williams, Vinod Koul, Zhuangluan Su,
	Ryan Grachek, Manivannan Sadhasivam, dmaengine, Tanglei Han,
	John Stultz

From: Youlin Wang <wwx575822@notesmail.huawei.com>

On the hi3660 hardware there are two (at least) DMA controllers,
the DMA-P (Peripheral DMA) and the DMA-A (Audio DMA). The
two blocks are similar, but have some slight differences. This
resulted in the vendor implementing two separate drivers, which
after review, they have been able to condense and re-use the
existing k3dma driver.

Thus, this patch adds support for the new "hisi-pcm-asp-dma-1.0"
compatible string in the binding.

One difference with the DMA-A controller, is that it does not
need to initialize a clock. So we skip this by adding and using
soc data flags.

After above this driver will support both k3 and hisi_asp dma
hardware.

Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Zhuangluan Su <suzhuangluan@hisilicon.com>
Cc: Ryan Grachek <ryan@edited.us>
Cc: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Cc: dmaengine@vger.kernel.org
Acked-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Signed-off-by: Youlin Wang <wwx575822@notesmail.huawei.com>
Signed-off-by: Tanglei Han <hantanglei@huawei.com>
[jstultz: Reworked to use of_match_data, commit msg improvements]
Signed-off-by: John Stultz <john.stultz@linaro.org>
---
v2:
* Reworked to use of_match_data
v3:
* Further rework of the commit message
v5:
* Typo, whitespace fixes. Use BIT() macro.
---
 drivers/dma/k3dma.c | 38 +++++++++++++++++++++++++++++++++-----
 1 file changed, 33 insertions(+), 5 deletions(-)

diff --git a/drivers/dma/k3dma.c b/drivers/dma/k3dma.c
index fdec2b6..4dce532 100644
--- a/drivers/dma/k3dma.c
+++ b/drivers/dma/k3dma.c
@@ -116,6 +116,14 @@ struct k3_dma_dev {
 	unsigned int		irq;
 };
 
+
+#define K3_FLAG_NOCLK	BIT(1)
+
+struct k3dma_soc_data {
+	unsigned long flags;
+};
+
+
 #define to_k3_dma(dmadev) container_of(dmadev, struct k3_dma_dev, slave)
 
 static int k3_dma_config_write(struct dma_chan *chan,
@@ -790,8 +798,21 @@ static int k3_dma_transfer_resume(struct dma_chan *chan)
 	return 0;
 }
 
+static const struct k3dma_soc_data k3_v1_dma_data = {
+	.flags = 0,
+};
+
+static const struct k3dma_soc_data asp_v1_dma_data = {
+	.flags = K3_FLAG_NOCLK,
+};
+
 static const struct of_device_id k3_pdma_dt_ids[] = {
-	{ .compatible = "hisilicon,k3-dma-1.0", },
+	{ .compatible = "hisilicon,k3-dma-1.0",
+	  .data = &k3_v1_dma_data
+	},
+	{ .compatible = "hisilicon,hisi-pcm-asp-dma-1.0",
+	  .data = &asp_v1_dma_data
+	},
 	{}
 };
 MODULE_DEVICE_TABLE(of, k3_pdma_dt_ids);
@@ -810,6 +831,7 @@ static struct dma_chan *k3_of_dma_simple_xlate(struct of_phandle_args *dma_spec,
 
 static int k3_dma_probe(struct platform_device *op)
 {
+	const struct k3dma_soc_data *soc_data;
 	struct k3_dma_dev *d;
 	const struct of_device_id *of_id;
 	struct resource *iores;
@@ -823,6 +845,10 @@ static int k3_dma_probe(struct platform_device *op)
 	if (!d)
 		return -ENOMEM;
 
+	soc_data = device_get_match_data(&op->dev);
+	if (!soc_data)
+		return -EINVAL;
+
 	d->base = devm_ioremap_resource(&op->dev, iores);
 	if (IS_ERR(d->base))
 		return PTR_ERR(d->base);
@@ -835,10 +861,12 @@ static int k3_dma_probe(struct platform_device *op)
 				"dma-requests", &d->dma_requests);
 	}
 
-	d->clk = devm_clk_get(&op->dev, NULL);
-	if (IS_ERR(d->clk)) {
-		dev_err(&op->dev, "no dma clk\n");
-		return PTR_ERR(d->clk);
+	if (!(soc_data->flags & K3_FLAG_NOCLK)) {
+		d->clk = devm_clk_get(&op->dev, NULL);
+		if (IS_ERR(d->clk)) {
+			dev_err(&op->dev, "no dma clk\n");
+			return PTR_ERR(d->clk);
+		}
 	}
 
 	irq = platform_get_irq(op, 0);

^ permalink raw reply related

* [2/8,v5] Documentation: bindings: dma: Add binding for dma-channel-mask
From: John Stultz @ 2019-01-24 20:24 UTC (permalink / raw)
  To: lkml
  Cc: John Stultz, Vinod Koul, Rob Herring, Mark Rutland, Tanglei Han,
	Zhuangluan Su, Ryan Grachek, Manivannan Sadhasivam, dmaengine,
	devicetree

Some dma channels can be reserved for secure mode or other
hardware on the SoC, so provide a binding for a bitmask
listing the available channels for the kernel to use.

This follows the pre-existing bcm,dma-channel-mask binding.

Cc: Vinod Koul <vkoul@kernel.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Tanglei Han <hantanglei@huawei.com>
Cc: Zhuangluan Su <suzhuangluan@hisilicon.com>
Cc: Ryan Grachek <ryan@edited.us>
Cc: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Cc: dmaengine@vger.kernel.org
Cc: devicetree@vger.kernel.org
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: John Stultz <john.stultz@linaro.org>
---
v3: Renamed to hisi-dma-avail-chan
v4: Reworked to generic dma-channel-mask
---
 Documentation/devicetree/bindings/dma/dma.txt | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Documentation/devicetree/bindings/dma/dma.txt b/Documentation/devicetree/bindings/dma/dma.txt
index 6312fb0..eeb4e4d 100644
--- a/Documentation/devicetree/bindings/dma/dma.txt
+++ b/Documentation/devicetree/bindings/dma/dma.txt
@@ -16,6 +16,9 @@ Optional properties:
 - dma-channels: 	Number of DMA channels supported by the controller.
 - dma-requests: 	Number of DMA request signals supported by the
 			controller.
+- dma-channel-mask:	Bitmask of available DMA channels in ascending order
+			that are not reserved by firmware and are available to
+			the kernel. i.e. first channel corresponds to LSB.
 
 Example:
 
@@ -29,6 +32,7 @@ Example:
 		#dma-cells = <1>;
 		dma-channels = <32>;
 		dma-requests = <127>;
+		dma-channel-mask = <0xfffe>
 	};
 
 * DMA router

^ permalink raw reply related

* [1/8,v5] Documentation: bindings: k3dma: Extend the k3dma driver binding to support hisi-asp
From: John Stultz @ 2019-01-24 20:24 UTC (permalink / raw)
  To: lkml
  Cc: Youlin Wang, Vinod Koul, Rob Herring, Mark Rutland, Zhuangluan Su,
	Tanglei Han, Ryan Grachek, Manivannan Sadhasivam, dmaengine,
	devicetree, John Stultz

From: Youlin Wang <wwx575822@notesmail.huawei.com>

Extend the k3dma driver binding to support hisi-asp hardware
variants.

Cc: Vinod Koul <vkoul@kernel.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Zhuangluan Su <suzhuangluan@hisilicon.com>
Cc: Tanglei Han <hantanglei@huawei.com>
Cc: Ryan Grachek <ryan@edited.us>
Cc: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Cc: dmaengine@vger.kernel.org
Cc: devicetree@vger.kernel.org
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Youlin Wang <wwx575822@notesmail.huawei.com>
Signed-off-by: Tanglei Han <hantanglei@huawei.com>
Signed-off-by: John Stultz <john.stultz@linaro.org>
---
v2: Simplify patch, removing extranious examples
---
 Documentation/devicetree/bindings/dma/k3dma.txt | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/dma/k3dma.txt b/Documentation/devicetree/bindings/dma/k3dma.txt
index 4945aea..10a2f15 100644
--- a/Documentation/devicetree/bindings/dma/k3dma.txt
+++ b/Documentation/devicetree/bindings/dma/k3dma.txt
@@ -3,7 +3,9 @@
 See dma.txt first
 
 Required properties:
-- compatible: Should be "hisilicon,k3-dma-1.0"
+- compatible: Must be one of
+-              "hisilicon,k3-dma-1.0"
+-              "hisilicon,hisi-pcm-asp-dma-1.0"
 - reg: Should contain DMA registers location and length.
 - interrupts: Should contain one interrupt shared by all channel
 - #dma-cells: see dma.txt, should be 1, para number

^ permalink raw reply related

* [2/2] dmaengine: mediatek-cqdma: remove redundant queue structure
From: shun-chih.yu @ 2019-01-24 10:44 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>

This patch introduces active_vdec to indicate the virtual descriptor
under processing by the CQDMA dmaengine, and simplify the control logic
by removing redundant queue structure, tasklets, and completion
management.

Also, wrong residue assignment in mtk_cqdma_tx_status and typos are
fixed.

Signed-off-by: Shun-Chih Yu <shun-chih.yu@mediatek.com>
---
 drivers/dma/mediatek/mtk-cqdma.c |  399 ++++++++++----------------------------
 1 file changed, 98 insertions(+), 301 deletions(-)

diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
index 131f397..387781b 100644
--- a/drivers/dma/mediatek/mtk-cqdma.c
+++ b/drivers/dma/mediatek/mtk-cqdma.c
@@ -22,6 +22,7 @@
 #include <linux/of_dma.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/preempt.h>
 #include <linux/refcount.h>
 #include <linux/slab.h>
 
@@ -47,7 +48,6 @@
 #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
 
@@ -69,45 +69,32 @@
  *                         descriptor (CVD)
  * @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
- * @node:                  The lise_head struct to build link-list for VDs
- * @parent:                The pointer to the parent CVD
  */
 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;
-
-	struct list_head node;
-	struct mtk_cqdma_vdesc *parent;
 };
 
 /**
  * struct mtk_cqdma_pchan - The struct holding info describing physical
  *                         channel (PC)
- * @queue:                 Queue for the PDs issued to this PC
+ * @active_vdesc:          The pointer to the CVD which is under processing
  * @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
- * @tasklet:               Tasklet for this PC
  * @lock:                  Lock protect agaisting multiple VCs access PC
  */
 struct mtk_cqdma_pchan {
-	struct list_head queue;
+	struct mtk_cqdma_vdesc *active_vdesc;
 	void __iomem *base;
 	u32 irq;
-
 	refcount_t refcnt;
-
-	struct tasklet_struct tasklet;
-
-	/* lock to protect PC */
 	spinlock_t lock;
 };
 
@@ -116,14 +103,10 @@ struct mtk_cqdma_pchan {
  *                         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;
 };
 
 /**
@@ -168,7 +151,7 @@ static struct device *cqdma2dev(struct mtk_cqdma_device *cqdma)
 
 static u32 mtk_dma_read(struct mtk_cqdma_pchan *pc, u32 reg)
 {
-	return readl(pc->base + reg);
+	return readl_relaxed(pc->base + reg);
 }
 
 static void mtk_dma_write(struct mtk_cqdma_pchan *pc, u32 reg, u32 val)
@@ -202,22 +185,22 @@ static void mtk_cqdma_vdesc_free(struct virt_dma_desc *vd)
 	kfree(to_cqdma_vdesc(vd));
 }
 
-static int mtk_cqdma_poll_engine_done(struct mtk_cqdma_pchan *pc, bool atomic)
+static int mtk_cqdma_poll_engine_done(struct mtk_cqdma_pchan *pc)
 {
 	u32 status = 0;
 
-	if (!atomic)
+	if (!in_task())
 		return readl_poll_timeout(pc->base + MTK_CQDMA_EN,
 					  status,
 					  !(status & MTK_CQDMA_EN_BIT),
 					  MTK_CQDMA_USEC_POLL,
 					  MTK_CQDMA_TIMEOUT_POLL);
-
-	return readl_poll_timeout_atomic(pc->base + MTK_CQDMA_EN,
-					 status,
-					 !(status & MTK_CQDMA_EN_BIT),
-					 MTK_CQDMA_USEC_POLL,
-					 MTK_CQDMA_TIMEOUT_POLL);
+	else
+		return readl_poll_timeout_atomic(pc->base + MTK_CQDMA_EN,
+						 status,
+						 !(status & MTK_CQDMA_EN_BIT),
+						 MTK_CQDMA_USEC_POLL,
+						 MTK_CQDMA_TIMEOUT_POLL);
 }
 
 static int mtk_cqdma_hard_reset(struct mtk_cqdma_pchan *pc)
@@ -225,20 +208,17 @@ 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, false);
+	return mtk_cqdma_poll_engine_done(pc);
 }
 
 static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc,
 			    struct mtk_cqdma_vdesc *cvd)
 {
-	/* wait for the previous transaction done */
-	if (mtk_cqdma_poll_engine_done(pc, true) < 0)
-		dev_err(cqdma2dev(to_cqdma_dev(cvd->ch)), "cqdma wait transaction timeout\n");
-
 	/* warm reset the dma engine for the new transaction */
 	mtk_dma_set(pc, MTK_CQDMA_RESET, MTK_CQDMA_WARM_RST_BIT);
-	if (mtk_cqdma_poll_engine_done(pc, true) < 0)
-		dev_err(cqdma2dev(to_cqdma_dev(cvd->ch)), "cqdma warm reset timeout\n");
+	if (mtk_cqdma_poll_engine_done(pc) < 0)
+		dev_err(cqdma2dev(to_cqdma_dev(cvd->ch)),
+			"cqdma warm reset timeout\n");
 
 	/* setup the source */
 	mtk_dma_set(pc, MTK_CQDMA_SRC, cvd->src & MTK_CQDMA_ADDR_LIMIT);
@@ -253,11 +233,12 @@ static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc,
 #ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
 	mtk_dma_set(pc, MTK_CQDMA_DST2, cvd->dest >> MTK_CQDMA_ADDR2_SHFIT);
 #else
-	mtk_dma_set(pc, MTK_CQDMA_SRC2, 0);
+	mtk_dma_set(pc, MTK_CQDMA_DST2, 0);
 #endif
 
 	/* setup the length */
-	mtk_dma_set(pc, MTK_CQDMA_LEN1, cvd->len);
+	mtk_dma_set(pc, MTK_CQDMA_LEN1, (cvd->len < MTK_CQDMA_MAX_LEN) ?
+		    cvd->len : MTK_CQDMA_MAX_LEN);
 
 	/* start dma engine */
 	mtk_dma_set(pc, MTK_CQDMA_EN, MTK_CQDMA_EN_BIT);
@@ -265,30 +246,17 @@ static void mtk_cqdma_start(struct mtk_cqdma_pchan *pc,
 
 static void mtk_cqdma_issue_vchan_pending(struct mtk_cqdma_vchan *cvc)
 {
-	struct virt_dma_desc *vd, *vd2;
+	struct virt_dma_desc *vd;
 	struct mtk_cqdma_pchan *pc = cvc->pc;
-	struct mtk_cqdma_vdesc *cvd;
-	bool trigger_engine = false;
 
 	lockdep_assert_held(&cvc->vc.lock);
 	lockdep_assert_held(&pc->lock);
 
-	list_for_each_entry_safe(vd, vd2, &cvc->vc.desc_issued, node) {
-		/* need to trigger dma engine if PC's queue is empty */
-		if (list_empty(&pc->queue))
-			trigger_engine = true;
-
-		cvd = to_cqdma_vdesc(vd);
+	vd = vchan_next_desc(&cvc->vc);
 
-		/* add VD into PC's queue */
-		list_add_tail(&cvd->node, &pc->queue);
-
-		/* start the dma engine */
-		if (trigger_engine)
-			mtk_cqdma_start(pc, cvd);
-
-		/* remove VD from list desc_issued */
-		list_del(&vd->node);
+	if (vd && !pc->active_vdesc) {
+		pc->active_vdesc = to_cqdma_vdesc(vd);
+		mtk_cqdma_start(pc, pc->active_vdesc);
 	}
 }
 
@@ -298,100 +266,55 @@ static void mtk_cqdma_issue_vchan_pending(struct mtk_cqdma_vchan *cvc)
  */
 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;
+	return (!cvc->pc->active_vdesc) ? false :
+	       (cvc == to_cqdma_vchan(cvc->pc->active_vdesc->ch));
 }
 
-/*
- * return the pointer of the CVD that is just consumed by the PC
- */
-static struct mtk_cqdma_vdesc
-*mtk_cqdma_consume_work_queue(struct mtk_cqdma_pchan *pc)
+static void mtk_cqdma_complete_vdesc(struct mtk_cqdma_pchan *pc)
 {
 	struct mtk_cqdma_vchan *cvc;
-	struct mtk_cqdma_vdesc *cvd, *ret = NULL;
-
-	/* consume a CVD from PC's queue */
-	cvd = list_first_entry_or_null(&pc->queue,
-				       struct mtk_cqdma_vdesc, node);
-	if (unlikely(!cvd || !cvd->parent))
-		return NULL;
+	struct mtk_cqdma_vdesc *cvd;
+	struct virt_dma_desc *vd;
+	size_t tlen;
 
+	cvd = pc->active_vdesc;
 	cvc = to_cqdma_vchan(cvd->ch);
-	ret = cvd;
-
-	/* update residue of the parent CVD */
-	cvd->parent->residue -= cvd->len;
 
-	/* delete CVD from PC's queue */
-	list_del(&cvd->node);
+	tlen = (cvd->len < MTK_CQDMA_MAX_LEN) ? cvd->len : MTK_CQDMA_MAX_LEN;
+	cvd->len -= tlen;
+	cvd->src += tlen;
+	cvd->dest += tlen;
 
 	spin_lock(&cvc->vc.lock);
 
-	/* check whether all the child CVDs completed */
-	if (!cvd->parent->residue) {
-		/* add the parent VD into list desc_completed */
-		vchan_cookie_complete(&cvd->parent->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);
-
-	/* start transaction for next CVD in the queue */
-	cvd = list_first_entry_or_null(&pc->queue,
-				       struct mtk_cqdma_vdesc, node);
-	if (cvd)
-		mtk_cqdma_start(pc, cvd);
+	/* check whether the VD completed */
+	if (!cvd->len) {
+		/* delete VD from desc_issued */
+		list_del(&cvd->vd.node);
 
-	return ret;
-}
-
-static void mtk_cqdma_tasklet_cb(unsigned long data)
-{
-	struct mtk_cqdma_pchan *pc = (struct mtk_cqdma_pchan *)data;
-	struct mtk_cqdma_vdesc *cvd = NULL;
-	unsigned long flags;
+		/* add the VD into list desc_completed */
+		vchan_cookie_complete(&cvd->vd);
 
-	spin_lock_irqsave(&pc->lock, flags);
-	/* consume the queue */
-	cvd = mtk_cqdma_consume_work_queue(pc);
-	spin_unlock_irqrestore(&pc->lock, flags);
-
-	/* submit the next CVD */
-	if (cvd) {
-		dma_run_dependencies(&cvd->vd.tx);
-
-		/*
-		 * free child CVD after completion.
-		 * the parent CVD would be freeed with desc_free by user.
-		 */
-		if (cvd->parent != cvd)
-			kfree(cvd);
+		/* get the next active VD */
+		vd = vchan_next_desc(&cvc->vc);
+		pc->active_vdesc = (!vd) ? NULL : to_cqdma_vdesc(vd);
 	}
 
-	/* re-enable interrupt before leaving tasklet */
-	enable_irq(pc->irq);
+	/* start the next transaction */
+	if (pc->active_vdesc)
+		mtk_cqdma_start(pc, pc->active_vdesc);
+
+	spin_unlock(&cvc->vc.lock);
 }
 
 static irqreturn_t mtk_cqdma_irq(int irq, void *devid)
 {
 	struct mtk_cqdma_device *cqdma = devid;
 	irqreturn_t ret = IRQ_NONE;
-	bool schedule_tasklet = false;
 	u32 i;
 
 	/* clear interrupt flags for each PC */
-	for (i = 0; i < cqdma->dma_channels; ++i, schedule_tasklet = false) {
+	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) {
@@ -399,72 +322,21 @@ static irqreturn_t mtk_cqdma_irq(int irq, void *devid)
 			mtk_dma_clr(cqdma->pc[i], MTK_CQDMA_INT_FLAG,
 				    MTK_CQDMA_INT_FLAG_BIT);
 
-			schedule_tasklet = true;
+			mtk_cqdma_complete_vdesc(cqdma->pc[i]);
+
 			ret = IRQ_HANDLED;
 		}
 		spin_unlock(&cqdma->pc[i]->lock);
-
-		if (schedule_tasklet) {
-			/* disable interrupt */
-			disable_irq_nosync(cqdma->pc[i]->irq);
-
-			/* schedule the tasklet to handle the transactions */
-			tasklet_schedule(&cqdma->pc[i]->tasklet);
-		}
 	}
 
 	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;
+	return dma_cookie_status(c, cookie, txstate);
 }
 
 static void mtk_cqdma_issue_pending(struct dma_chan *c)
@@ -473,7 +345,7 @@ static void mtk_cqdma_issue_pending(struct dma_chan *c)
 	unsigned long pc_flags;
 	unsigned long vc_flags;
 
-	/* acquire PC's lock before VS's lock for lock dependency in tasklet */
+	/* acquire PC's lock before VC's lock for lock dependency in ISR */
 	spin_lock_irqsave(&cvc->pc->lock, pc_flags);
 	spin_lock_irqsave(&cvc->vc.lock, vc_flags);
 
@@ -488,124 +360,56 @@ static void mtk_cqdma_issue_pending(struct dma_chan *c)
 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;
-	struct dma_async_tx_descriptor *tx = NULL, *prev_tx = NULL;
-	size_t i, tlen, nr_vd;
-
-	/*
-	 * In the case that trsanction length is larger than the
-	 * DMA engine supports, a single memcpy transaction needs
-	 * to be separated into several DMA transactions.
-	 * Each DMA transaction would be described by a CVD,
-	 * and the first one is referred as the parent CVD,
-	 * while the others are child CVDs.
-	 * The parent CVD's tx descriptor is the only tx descriptor
-	 * returned to the DMA user, and it should not be completed
-	 * until all the child CVDs completed.
-	 */
-	nr_vd = DIV_ROUND_UP(len, MTK_CQDMA_MAX_LEN);
-	cvd = kcalloc(nr_vd, sizeof(*cvd), GFP_NOWAIT);
+	struct mtk_cqdma_vdesc *cvd;
+
+	cvd = kzalloc(sizeof(*cvd), GFP_NOWAIT);
 	if (!cvd)
 		return NULL;
 
-	for (i = 0; i < nr_vd; ++i) {
-		cvd[i] = kzalloc(sizeof(*cvd[i]), GFP_NOWAIT);
-		if (!cvd[i]) {
-			for (; i > 0; --i)
-				kfree(cvd[i - 1]);
-			return NULL;
-		}
+	/* setup dma channel */
+	cvd->ch = c;
 
-		/* setup dma channel */
-		cvd[i]->ch = c;
+	/* setup sourece, destination, and length */
+	cvd->len = len;
+	cvd->src = src;
+	cvd->dest = dest;
 
-		/* setup sourece, destination, and length */
-		tlen = (len > MTK_CQDMA_MAX_LEN) ? MTK_CQDMA_MAX_LEN : len;
-		cvd[i]->len = tlen;
-		cvd[i]->src = src;
-		cvd[i]->dest = dest;
-
-		/* setup tx descriptor */
-		tx = vchan_tx_prep(to_virt_chan(c), &cvd[i]->vd, flags);
-		tx->next = NULL;
-
-		if (!i) {
-			cvd[0]->residue = len;
-		} else {
-			prev_tx->next = tx;
-			cvd[i]->residue = tlen;
-		}
-
-		cvd[i]->parent = cvd[0];
-
-		/* update the src, dest, len, prev_tx for the next CVD */
-		src += tlen;
-		dest += tlen;
-		len -= tlen;
-		prev_tx = tx;
-	}
-
-	return &cvd[0]->vd.tx;
+	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)
+static int mtk_cqdma_terminate_all(struct dma_chan *c)
 {
 	struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
-	bool sync_needed = false;
+	struct virt_dma_chan *vc = to_virt_chan(c);
 	unsigned long pc_flags;
 	unsigned long vc_flags;
+	LIST_HEAD(head);
 
-	/* 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;
-	}
+	do {
+		/* 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);
 
-	spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
-	spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
+		/* wait for the VC to be inactive  */
+		if (mtk_cqdma_is_vchan_active(cvc)) {
+			spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
+			spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
+			continue;
+		}
 
-	/* waiting for the completion of this VC */
-	if (sync_needed)
-		wait_for_completion(&cvc->issue_completion);
+		/* get VDs from lists */
+		vchan_get_all_descriptors(vc, &head);
 
-	/* free all descriptors in list desc_completed */
-	vchan_synchronize(&cvc->vc);
+		/* free all the VDs */
+		vchan_dma_desc_free_list(vc, &head);
 
-	WARN_ONCE(!list_empty(&cvc->vc.desc_completed),
-		  "Desc pending still in list desc_completed\n");
-}
+		spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
+		spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
 
-static int mtk_cqdma_terminate_all(struct dma_chan *c)
-{
-	/* free descriptors not processed yet by hardware */
-	mtk_cqdma_free_inactive_desc(c);
+		break;
+	} while (1);
 
-	/* free descriptors being processed by hardware */
-	mtk_cqdma_free_active_desc(c);
+	vchan_synchronize(&cvc->vc);
 
 	return 0;
 }
@@ -618,7 +422,7 @@ static int mtk_cqdma_alloc_chan_resources(struct dma_chan *c)
 	u32 i, min_refcnt = U32_MAX, refcnt;
 	unsigned long flags;
 
-	/* allocate PC with the minimun refcount */
+	/* allocate PC with the minimum refcount */
 	for (i = 0; i < cqdma->dma_channels; ++i) {
 		refcnt = refcount_read(&cqdma->pc[i]->refcnt);
 		if (refcnt < min_refcnt) {
@@ -671,8 +475,9 @@ static void mtk_cqdma_free_chan_resources(struct dma_chan *c)
 		mtk_dma_set(cvc->pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
 
 		/* wait for the completion of flush operation */
-		if (mtk_cqdma_poll_engine_done(cvc->pc, false) < 0)
-			dev_err(cqdma2dev(to_cqdma_dev(c)), "cqdma flush timeout\n");
+		if (mtk_cqdma_poll_engine_done(cvc->pc) < 0)
+			dev_err(cqdma2dev(to_cqdma_dev(c)),
+				"cqdma flush timeout\n");
 
 		/* clear the flush bit and interrupt flag */
 		mtk_dma_clr(cvc->pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
@@ -787,9 +592,9 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
 	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);
+		dev_dbg(&pdev->dev,
+			"Using %u as missing dma-requests property\n",
+			MTK_CQDMA_NR_VCHANS);
 
 		cqdma->dma_requests = MTK_CQDMA_NR_VCHANS;
 	}
@@ -797,9 +602,9 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
 	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);
+		dev_dbg(&pdev->dev,
+			"Using %u as missing dma-channels property\n",
+			MTK_CQDMA_NR_PCHANS);
 
 		cqdma->dma_channels = MTK_CQDMA_NR_PCHANS;
 	}
@@ -816,7 +621,7 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
 		if (!cqdma->pc[i])
 			return -ENOMEM;
 
-		INIT_LIST_HEAD(&cqdma->pc[i]->queue);
+		cqdma->pc[i]->active_vdesc = NULL;
 		spin_lock_init(&cqdma->pc[i]->lock);
 		refcount_set(&cqdma->pc[i]->refcnt, 0);
 
@@ -860,7 +665,6 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
 		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);
@@ -884,12 +688,7 @@ static int mtk_cqdma_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, cqdma);
 
-	/* initialize tasklet for each PC */
-	for (i = 0; i < cqdma->dma_channels; ++i)
-		tasklet_init(&cqdma->pc[i]->tasklet, mtk_cqdma_tasklet_cb,
-			     (unsigned long)cqdma->pc[i]);
-
-	dev_info(&pdev->dev, "MediaTek CQDMA driver registered\n");
+	dev_dbg(&pdev->dev, "MediaTek CQDMA driver registered\n");
 
 	return 0;
 
@@ -923,8 +722,6 @@ static int mtk_cqdma_remove(struct platform_device *pdev)
 
 		/* Waits for any pending IRQ handlers to complete */
 		synchronize_irq(cqdma->pc[i]->irq);
-
-		tasklet_kill(&cqdma->pc[i]->tasklet);
 	}
 
 	/* disable hardware */

^ permalink raw reply related

* [1/2] dt-bindings: dmaengine: Add MediaTek Command-Queue DMA controller bindings
From: shun-chih.yu @ 2019-01-24 10:44 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>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 .../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

* [v5,2/2] dmaengine: mediatek: Add MediaTek Command-Queue DMA controller for MT6765 SoC
From: shun-chih.yu @ 2019-01-24  7:14 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 |  748 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 761 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..0009260
--- /dev/null
+++ b/drivers/dma/mediatek/mtk-cqdma.c
@@ -0,0 +1,748 @@
+// 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/interrupt.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/preempt.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		32
+
+/* 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_FLUSH			0x14
+#define MTK_CQDMA_SRC			0x1c
+#define MTK_CQDMA_DST			0x20
+#define MTK_CQDMA_LEN1			0x24
+#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		GENMASK(27, 0)
+#define MTK_CQDMA_ADDR_LIMIT		GENMASK(31, 0)
+#define MTK_CQDMA_ADDR2_SHFIT		(32)
+
+/**
+ * struct mtk_cqdma_vdesc - The struct holding info describing virtual
+ *                         descriptor (CVD)
+ * @vd:                    An instance for struct virt_dma_desc
+ * @len:                   The total data size device wants to 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
+ */
+struct mtk_cqdma_vdesc {
+	struct virt_dma_desc vd;
+	size_t len;
+	dma_addr_t dest;
+	dma_addr_t src;
+	struct dma_chan *ch;
+};
+
+/**
+ * struct mtk_cqdma_pchan - The struct holding info describing physical
+ *                         channel (PC)
+ * @active_vdesc:          Pointer to the CVD which is under hardware processing
+ * @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 mtk_cqdma_vdesc *active_vdesc;
+	void __iomem *base;
+	u32 irq;
+	refcount_t refcnt;
+	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
+ */
+struct mtk_cqdma_vchan {
+	struct virt_dma_chan vc;
+	struct mtk_cqdma_pchan *pc;
+};
+
+/**
+ * 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_relaxed(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)
+{
+	kfree(to_cqdma_vdesc(vd));
+}
+
+static int mtk_cqdma_poll_engine_done(struct mtk_cqdma_pchan *pc)
+{
+	u32 status = 0;
+
+	if (!in_task())
+		return readl_poll_timeout(pc->base + MTK_CQDMA_EN,
+					  status,
+					  !(status & MTK_CQDMA_EN_BIT),
+					  MTK_CQDMA_USEC_POLL,
+					  MTK_CQDMA_TIMEOUT_POLL);
+	else
+		return readl_poll_timeout_atomic(pc->base + MTK_CQDMA_EN,
+						 status,
+						 !(status & MTK_CQDMA_EN_BIT),
+						 MTK_CQDMA_USEC_POLL,
+						 MTK_CQDMA_TIMEOUT_POLL);
+}
+
+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,
+			    struct mtk_cqdma_vdesc *cvd)
+{
+	/* warm reset the dma engine for the new transaction */
+	mtk_dma_set(pc, MTK_CQDMA_RESET, MTK_CQDMA_WARM_RST_BIT);
+	if (mtk_cqdma_poll_engine_done(pc) < 0)
+		dev_err(cqdma2dev(to_cqdma_dev(cvd->ch)),
+			"cqdma warm reset timeout\n");
+
+	/* setup the source */
+	mtk_dma_set(pc, MTK_CQDMA_SRC, cvd->src & MTK_CQDMA_ADDR_LIMIT);
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+	mtk_dma_set(pc, MTK_CQDMA_SRC2, cvd->src >> MTK_CQDMA_ADDR2_SHFIT);
+#else
+	mtk_dma_set(pc, MTK_CQDMA_SRC2, 0);
+#endif
+
+	/* setup the destination */
+	mtk_dma_set(pc, MTK_CQDMA_DST, cvd->dest & MTK_CQDMA_ADDR_LIMIT);
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+	mtk_dma_set(pc, MTK_CQDMA_DST2, cvd->dest >> MTK_CQDMA_ADDR2_SHFIT);
+#else
+	mtk_dma_set(pc, MTK_CQDMA_DST2, 0);
+#endif
+
+	/* setup the length */
+	mtk_dma_set(pc, MTK_CQDMA_LEN1, (cvd->len < MTK_CQDMA_MAX_LEN) ?
+		    cvd->len : MTK_CQDMA_MAX_LEN);
+
+	/* start dma engine */
+	mtk_dma_set(pc, MTK_CQDMA_EN, MTK_CQDMA_EN_BIT);
+}
+
+static void mtk_cqdma_issue_vchan_pending(struct mtk_cqdma_vchan *cvc)
+{
+	struct virt_dma_desc *vd;
+	struct mtk_cqdma_pchan *pc = cvc->pc;
+
+	lockdep_assert_held(&cvc->vc.lock);
+	lockdep_assert_held(&pc->lock);
+
+	vd = vchan_next_desc(&cvc->vc);
+
+	if (vd && !pc->active_vdesc) {
+		pc->active_vdesc = to_cqdma_vdesc(vd);
+		mtk_cqdma_start(pc, pc->active_vdesc);
+	}
+}
+
+/*
+ * 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)
+{
+	return (!cvc->pc->active_vdesc) ? false :
+	       (cvc == to_cqdma_vchan(cvc->pc->active_vdesc->ch));
+}
+
+static void mtk_cqdma_complete_vdesc(struct mtk_cqdma_pchan *pc)
+{
+	struct mtk_cqdma_vchan *cvc;
+	struct mtk_cqdma_vdesc *cvd;
+	struct virt_dma_desc *vd;
+	size_t tlen;
+
+	cvd = pc->active_vdesc;
+	cvc = to_cqdma_vchan(cvd->ch);
+
+	tlen = (cvd->len < MTK_CQDMA_MAX_LEN) ? cvd->len : MTK_CQDMA_MAX_LEN;
+	cvd->len -= tlen;
+	cvd->src += tlen;
+	cvd->dest += tlen;
+
+	spin_lock(&cvc->vc.lock);
+
+	/* check whether the VD completed */
+	if (!cvd->len) {
+		/* delete VD from desc_issued */
+		list_del(&cvd->vd.node);
+
+		/* add the VD into list desc_completed */
+		vchan_cookie_complete(&cvd->vd);
+
+		/* get the next active VD */
+		vd = vchan_next_desc(&cvc->vc);
+		pc->active_vdesc = (!vd) ? NULL : to_cqdma_vdesc(vd);
+	}
+
+	/* start the next transaction */
+	if (pc->active_vdesc)
+		mtk_cqdma_start(pc, pc->active_vdesc);
+
+	spin_unlock(&cvc->vc.lock);
+}
+
+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);
+
+			mtk_cqdma_complete_vdesc(cqdma->pc[i]);
+
+			ret = IRQ_HANDLED;
+		}
+		spin_unlock(&cqdma->pc[i]->lock);
+	}
+
+	return ret;
+}
+
+static enum dma_status mtk_cqdma_tx_status(struct dma_chan *c,
+					   dma_cookie_t cookie,
+					   struct dma_tx_state *txstate)
+{
+	return dma_cookie_status(c, cookie, txstate);
+}
+
+static void mtk_cqdma_issue_pending(struct dma_chan *c)
+{
+	struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
+	unsigned long pc_flags;
+	unsigned long vc_flags;
+
+	/* acquire PC's lock before VC's lock for 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(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;
+
+	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->src = src;
+	cvd->dest = dest;
+
+	return vchan_tx_prep(to_virt_chan(c), &cvd->vd, flags);
+}
+
+static int mtk_cqdma_terminate_all(struct dma_chan *c)
+{
+	struct mtk_cqdma_vchan *cvc = to_cqdma_vchan(c);
+	struct virt_dma_chan *vc = to_virt_chan(c);
+	unsigned long pc_flags;
+	unsigned long vc_flags;
+	LIST_HEAD(head);
+
+	do {
+		/* 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);
+
+		/* wait for the VC to be inactive  */
+		if (mtk_cqdma_is_vchan_active(cvc)) {
+			spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
+			spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
+			continue;
+		}
+
+		/* get VDs from lists */
+		vchan_get_all_descriptors(vc, &head);
+
+		/* free all the VDs */
+		vchan_dma_desc_free_list(vc, &head);
+
+		spin_unlock_irqrestore(&cvc->vc.lock, vc_flags);
+		spin_unlock_irqrestore(&cvc->pc->lock, pc_flags);
+
+		break;
+	} while (1);
+
+	vchan_synchronize(&cvc->vc);
+
+	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 minimum 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_hard_reset(pc);
+
+		/* enable interrupt for this PC */
+		mtk_dma_set(pc, MTK_CQDMA_INT_EN, MTK_CQDMA_INT_EN_BIT);
+
+		/*
+		 * 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)) {
+		/* start the flush operation and stop the engine */
+		mtk_dma_set(cvc->pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
+
+		/* wait for the completion of flush operation */
+		if (mtk_cqdma_poll_engine_done(cvc->pc) < 0)
+			dev_err(cqdma2dev(to_cqdma_dev(c)),
+				"cqdma flush timeout\n");
+
+		/* clear the flush bit and interrupt flag */
+		mtk_dma_clr(cvc->pc, MTK_CQDMA_FLUSH, MTK_CQDMA_FLUSH_BIT);
+		mtk_dma_clr(cvc->pc, MTK_CQDMA_INT_FLAG,
+			    MTK_CQDMA_INT_FLAG_BIT);
+
+		/* disable interrupt for this PC */
+		mtk_dma_clr(cvc->pc, MTK_CQDMA_INT_EN, MTK_CQDMA_INT_EN_BIT);
+	}
+
+	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) {
+			dev_err(cqdma2dev(cqdma), "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)
+			dev_err(cqdma2dev(cqdma), "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_dbg(&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_dbg(&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;
+
+		cqdma->pc[i]->active_vdesc = NULL;
+		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);
+	}
+
+	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_dbg(&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

* [v5,1/2] dt-bindings: dmaengine: Add MediaTek Command-Queue DMA controller bindings
From: shun-chih.yu @ 2019-01-24  7:14 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>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 .../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/8,v4] dma: k3dma: Upgrade k3dma driver to support hisi_asp_dma hardware
From: John Stultz @ 2019-01-24  4:32 UTC (permalink / raw)
  To: Vinod Koul
  Cc: lkml, Youlin Wang, Dan Williams, Zhuangluan Su, Ryan Grachek,
	Manivannan Sadhasivam,
	open list:DMA GENERIC OFFLOAD ENGINE SUBSYSTEM, Tanglei Han

On Wed, Jan 23, 2019 at 4:57 AM Vinod Koul <vkoul@kernel.org> wrote:
> On 22-01-19, 15:48, John Stultz wrote:
> > Do let me know if there's an example you'd rather I follow.
>
> To elaborate I was thinking of alternate scheme with:
>
>         compatible = "hisilicon,k3-dma-1.0", NULL
>         compatible = "hisilicon,hisi-pcm-asp-dma-1.0", .data = &asp_v1_dma_data
>
> and
>
>         soc_data = device_get_match_data(&op->dev);
>         if (!soc_data) {
>                 /* no data so flags are null */
>                 dev_warn(... "no driver data specified, assuming  no flags\n"
>                 k3_dma->flags = 0;
>         }

Thanks for the clarification! Hmm. I can do this, though the trouble
is all the soc_data-> references have to switch to a struct
k3dma_soc_data on the stack, which we have to initialize/copy over
depending on the soc_data return, which I'm not sure really simplifies
much (other then saving a ulong off the heap). But I'm ok with either.

thanks
-john

^ permalink raw reply

* [v3,5/5] imx8mq.dtsi: add the sdma nodes
From: Lucas Stach @ 2019-01-23 16:50 UTC (permalink / raw)
  To: Angus Ainslie (Purism)
  Cc: angus.ainslie, Vinod Koul, dmaengine, NXP Linux Team,
	Pengutronix Kernel Team, linux-arm-kernel, linux-kernel,
	Daniel Baluta

Am Mittwoch, den 23.01.2019, 08:23 -0700 schrieb Angus Ainslie (Purism):
> Add the sdma nodes to the base devicetree for the imx8mq
> 
> > Signed-off-by: Angus Ainslie (Purism) <angus@akkea.ca>
> ---
>  arch/arm64/boot/dts/freescale/imx8mq.dtsi | 31 +++++++++++++++++++++++
>  1 file changed, 31 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> index c0402375e7c1..4397992fd021 100644
> --- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> +++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> @@ -336,6 +336,19 @@
> >  				clocks = <&clk IMX8MQ_CLK_WDOG3_ROOT>;
> >  				status = "disabled";
> >  			};
> +
> > > +			sdma2: sdma@302c0000 {
> > +				compatible = "fsl,imx8mq-sdma", "fsl,imx7d-sdma";
> > +				reg = <0x302c0000 0x10000>;
> > +				interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
> > +				clocks = <&clk IMX8MQ_CLK_SDMA2_ROOT>,
> > +					<&clk IMX8MQ_CLK_SDMA2_ROOT>;
> > +				clock-names = "ipg", "ahb";
> > +				#dma-cells = <3>;
> > +				fsl,sdma-ram-script-name = "imx/sdma/sdma-imx7d.bin";
> +				fsl,ratio-1-1;

Property does not exist anymore.

> +				status = "disabled";

SoC peripherals that have no outside connection and thus don't depend
on any board level configuration should always be enabled. Please drop
this status property.

> +			};
> >  		};
>  
> >  		bus@30400000 { /* AIPS2 */
> @@ -370,6 +383,8 @@
> >  				clocks = <&clk IMX8MQ_CLK_UART3_ROOT>,
> >  				         <&clk IMX8MQ_CLK_UART3_ROOT>;
> >  				clock-names = "ipg", "per";
> > +				dmas = <&sdma1 26 4 0>, <&sdma1 27 4 0>;
> > +				dma-names = "rx", "tx";
> >  				status = "disabled";
> >  			};
>  
> @@ -381,6 +396,8 @@
> >  				clocks = <&clk IMX8MQ_CLK_UART2_ROOT>,
> >  				         <&clk IMX8MQ_CLK_UART2_ROOT>;
> >  				clock-names = "ipg", "per";
> > +				dmas = <&sdma1 24 4 0>, <&sdma1 25 4 0>;
> > +				dma-names = "rx", "tx";
> >  				status = "disabled";
> >  			};
>  
> @@ -432,6 +449,8 @@
> >  				clocks = <&clk IMX8MQ_CLK_UART4_ROOT>,
> >  				         <&clk IMX8MQ_CLK_UART4_ROOT>;
> >  				clock-names = "ipg", "per";
> > +				dmas = <&sdma1 28 4 0>, <&sdma1 29 4 0>;
> > +				dma-names = "rx", "tx";
> >  				status = "disabled";
> >  			};
>  
> @@ -465,6 +484,18 @@
> >  				status = "disabled";
> >  			};
>  
> > > +			sdma1: sdma@30bd0000 {
> > +				compatible = "fsl,imx8mq-sdma", "fsl,imx7d-sdma";
> > +				reg = <0x30bd0000 0x10000>;
> > +				interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
> > +				clocks = <&clk IMX8MQ_CLK_SDMA1_ROOT>,
> > +					<&clk IMX8MQ_CLK_AHB>;
> > +				clock-names = "ipg", "ahb";
> > +				#dma-cells = <3>;
> > +				fsl,sdma-ram-script-name = "imx/sdma/sdma-imx7d.bin";
> +				status = "disabled";

Same comment as above, this node should be enabled by default.

Regards,
Lucas

> +			};
> +
> > >  			fec1: ethernet@30be0000 {
> >  				compatible = "fsl,imx8mq-fec", "fsl,imx6sx-fec";
> >  				reg = <0x30be0000 0x10000>;

^ permalink raw reply

* [v2] dmaengine: at_xdmac: Fix wrongfull report of a channel as in use
From: Codrin Ciubotariu @ 2019-01-23 16:41 UTC (permalink / raw)
  To: Ludovic.Desroches, vkoul; +Cc: dmaengine, linux-arm-kernel, linux-kernel

On 23.01.2019 18:33, Codrin Ciubotariu - M19940 wrote:
> From: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
> 
> atchan->status variable is used to store two different information:
>   - pass channel interrupts status from interrupt handler to tasklet;
>   - channel information like whether it is cyclic or paused;
> 
> This causes a bug when device_terminate_all() is called,
> (AT_XDMAC_CHAN_IS_CYCLIC cleared on atchan->status) and then a late End
> of Block interrupt arrives (AT_XDMAC_CIS_BIS), which sets bit 0 of
> atchan->status. Bit 0 is also used for AT_XDMAC_CHAN_IS_CYCLIC, so when
> a new descriptor for a cyclic transfer is created, the driver reports
> the channel as in use:
> 
> if (test_and_set_bit(AT_XDMAC_CHAN_IS_CYCLIC, &atchan->status)) {
> 	dev_err(chan2dev(chan), "channel currently used\n");
> 	return NULL;
> }
> 
> This patch fixes the bug by adding a different struct member to keep
> the interrupts status separated from the channel status bits.
> 
> Fixes: e1f7c9eee707 ("dmaengine: at_xdmac: creation of the atmel eXtended DMA Controller driver")
> Signed-off-by: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
> Acked-by: Ludovic Desroches <ludovic.desroches@microchip.com>
> ---

Forgot to add:

Changes in v2:
  - changed commit message;

If you need v3 for this let me know.

Best regards,
Codrin

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox