* [PATCH v1 15/15] arm64: defconfig: Enable SDMA on i.mx8mq/8mm
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
Enable SDMA support on i.mx8mq/8mm chips, including enabling
CONFIG_FW_LOADER_USER_HELPER/CONFIG_FW_LOADER_USER_HELPER_FALLBACK
for firmware loaded by udev.
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
arch/arm64/configs/defconfig | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 17daa97..7081817 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -203,6 +203,8 @@ CONFIG_NET_9P_VIRTIO=y
CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_FW_LOADER_USER_HELPER=y
+CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y
CONFIG_DMA_CMA=y
CONFIG_CMA_SIZE_MBYTES=32
CONFIG_HISILICON_LPC=y
@@ -635,6 +637,7 @@ CONFIG_RTC_DRV_IMX_SC=m
CONFIG_RTC_DRV_XGENE=y
CONFIG_DMADEVICES=y
CONFIG_DMA_BCM2835=m
+CONFIG_IMX_SDMA=y
CONFIG_K3_DMA=y
CONFIG_MV_XOR_V2=y
CONFIG_PL330_DMA=y
--
2.7.4
^ permalink raw reply related
* [PATCH v1 14/15] ARM: dts: imx6sll: correct ecspi/sdma compatible
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
Correct ecspi/sdma compatible since ecspi errata ERR009165
not fixed on i.mx6sll chip.
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
arch/arm/boot/dts/imx6sll.dtsi | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/arch/arm/boot/dts/imx6sll.dtsi b/arch/arm/boot/dts/imx6sll.dtsi
index 1b4899f..03f2103 100644
--- a/arch/arm/boot/dts/imx6sll.dtsi
+++ b/arch/arm/boot/dts/imx6sll.dtsi
@@ -183,7 +183,7 @@
};
ecspi1: spi@2008000 {
- compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
+ compatible ="fsl,imx51-ecspi";
reg = <0x02008000 0x4000>;
interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;
@@ -195,7 +195,7 @@
};
ecspi2: spi@200c000 {
- compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx51-ecspi";
reg = <0x0200c000 0x4000>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&sdma 5 7 1>, <&sdma 6 7 2>;
@@ -207,7 +207,7 @@
};
ecspi3: spi@2010000 {
- compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx51-ecspi";
reg = <0x02010000 0x4000>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
@@ -219,7 +219,7 @@
};
ecspi4: spi@2014000 {
- compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx51-ecspi";
reg = <0x02014000 0x4000>;
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&sdma 9 7 1>, <&sdma 10 7 2>;
@@ -619,7 +619,7 @@
};
sdma: dma-controller@20ec000 {
- compatible = "fsl,imx6sll-sdma", "fsl,imx35-sdma";
+ compatible = "fsl,imx6q-sdma", "fsl,imx35-sdma";
reg = <0x020ec000 0x4000>;
interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6SLL_CLK_IPG>,
--
2.7.4
^ permalink raw reply related
* [PATCH v1 13/15] ARM: dts: imx6ul: add dma support on ecspi
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
Add dma support on ecspi.
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
arch/arm/boot/dts/imx6ul.dtsi | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm/boot/dts/imx6ul.dtsi b/arch/arm/boot/dts/imx6ul.dtsi
index bbf010c..880b9ee 100644
--- a/arch/arm/boot/dts/imx6ul.dtsi
+++ b/arch/arm/boot/dts/imx6ul.dtsi
@@ -226,6 +226,8 @@
clocks = <&clks IMX6UL_CLK_ECSPI1>,
<&clks IMX6UL_CLK_ECSPI1>;
clock-names = "ipg", "per";
+ dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;
+ dma-names = "rx", "tx";
status = "disabled";
};
@@ -238,6 +240,8 @@
clocks = <&clks IMX6UL_CLK_ECSPI2>,
<&clks IMX6UL_CLK_ECSPI2>;
clock-names = "ipg", "per";
+ dmas = <&sdma 5 7 1>, <&sdma 6 7 2>;
+ dma-names = "rx", "tx";
status = "disabled";
};
@@ -250,6 +254,8 @@
clocks = <&clks IMX6UL_CLK_ECSPI3>,
<&clks IMX6UL_CLK_ECSPI3>;
clock-names = "ipg", "per";
+ dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
+ dma-names = "rx", "tx";
status = "disabled";
};
@@ -262,6 +268,8 @@
clocks = <&clks IMX6UL_CLK_ECSPI4>,
<&clks IMX6UL_CLK_ECSPI4>;
clock-names = "ipg", "per";
+ dmas = <&sdma 9 7 1>, <&sdma 10 7 2>;
+ dma-names = "rx", "tx";
status = "disabled";
};
--
2.7.4
^ permalink raw reply related
* [PATCH v1 12/15] ARM64: dts: freescale: imx8mm/8mq: update new compatible name for ecspi and sdma
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
Add new 'imx6ul-ecspi' compatible name for ecspi and new 'imx8mq-sdma' name
for sdma since on i.mx8mm/mq chip fix ecspi errata.
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
arch/arm64/boot/dts/freescale/imx8mm.dtsi | 14 +++++++-------
arch/arm64/boot/dts/freescale/imx8mq.dtsi | 6 +++---
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/arch/arm64/boot/dts/freescale/imx8mm.dtsi b/arch/arm64/boot/dts/freescale/imx8mm.dtsi
index de3498c..1945aa3 100644
--- a/arch/arm64/boot/dts/freescale/imx8mm.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mm.dtsi
@@ -251,7 +251,7 @@
};
sdma2: dma-controller@302c0000 {
- compatible = "fsl,imx8mm-sdma", "fsl,imx7d-sdma";
+ compatible = "fsl,imx8mm-sdma", "fsl,imx8mq-sdma";
reg = <0x302c0000 0x10000>;
interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MM_CLK_SDMA2_ROOT>,
@@ -262,7 +262,7 @@
};
sdma3: dma-controller@302b0000 {
- compatible = "fsl,imx8mm-sdma", "fsl,imx7d-sdma";
+ compatible = "fsl,imx8mm-sdma", "fsl,imx8mq-sdma";
reg = <0x302b0000 0x10000>;
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MM_CLK_SDMA3_ROOT>,
@@ -393,7 +393,7 @@
ranges;
ecspi1: spi@30820000 {
- compatible = "fsl,imx8mm-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx8mm-ecspi", "fsl,imx6ul-ecspi";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x30820000 0x10000>;
@@ -407,7 +407,7 @@
};
ecspi2: spi@30830000 {
- compatible = "fsl,imx8mm-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx8mm-ecspi", "fsl,imx6ul-ecspi";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x30830000 0x10000>;
@@ -421,7 +421,7 @@
};
ecspi3: spi@30840000 {
- compatible = "fsl,imx8mm-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx8mm-ecspi", "fsl,imx6ul-ecspi";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x30840000 0x10000>;
@@ -567,11 +567,11 @@
};
sdma1: dma-controller@30bd0000 {
- compatible = "fsl,imx8mm-sdma", "fsl,imx7d-sdma";
+ compatible = "fsl,imx8mm-sdma", "fsl,imx8mq-sdma";
reg = <0x30bd0000 0x10000>;
interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MM_CLK_SDMA1_ROOT>,
- <&clk IMX8MM_CLK_SDMA1_ROOT>;
+ <&clk IMX8MM_CLK_AHB>;
clock-names = "ipg", "ahb";
#dma-cells = <3>;
fsl,sdma-ram-script-name = "imx/sdma/sdma-imx7d.bin";
diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
index 7c0b12a..f2a5d12 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
@@ -607,7 +607,7 @@
ecspi1: spi@30820000 {
#address-cells = <1>;
#size-cells = <0>;
- compatible = "fsl,imx8mq-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx8mq-ecspi", "fsl,imx6ul-ecspi";
reg = <0x30820000 0x10000>;
interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MQ_CLK_ECSPI1_ROOT>,
@@ -619,7 +619,7 @@
ecspi2: spi@30830000 {
#address-cells = <1>;
#size-cells = <0>;
- compatible = "fsl,imx8mq-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx8mq-ecspi", "fsl,imx6ul-ecspi";
reg = <0x30830000 0x10000>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MQ_CLK_ECSPI2_ROOT>,
@@ -631,7 +631,7 @@
ecspi3: spi@30840000 {
#address-cells = <1>;
#size-cells = <0>;
- compatible = "fsl,imx8mq-ecspi", "fsl,imx51-ecspi";
+ compatible = "fsl,imx8mq-ecspi", "fsl,imx6ul-ecspi";
reg = <0x30840000 0x10000>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MQ_CLK_ECSPI3_ROOT>,
--
2.7.4
^ permalink raw reply related
* [PATCH v1 11/15] dmaengine: imx-sdma: fix ecspi1 rx dma not work on i.mx8mm
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
Because the number of ecspi1 rx event on i.mx8mm is 0, the condition
check ignore such special case without dma channel enabled, which caused
ecspi1 rx works failed. Actually, no need to check event_id0, checking
event_id1 is enough for DEV_2_DEV case because it's so lucky that event_id1
never be 0.
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
drivers/dma/imx-sdma.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index 99c44a5..d87a8f9 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -1363,8 +1363,8 @@ static void sdma_free_chan_resources(struct dma_chan *chan)
sdma_channel_synchronize(chan);
- if (sdmac->event_id0)
- sdma_event_disable(sdmac, sdmac->event_id0);
+ sdma_event_disable(sdmac, sdmac->event_id0);
+
if (sdmac->event_id1)
sdma_event_disable(sdmac, sdmac->event_id1);
@@ -1663,11 +1663,9 @@ static int sdma_config(struct dma_chan *chan,
memcpy(&sdmac->slave_config, dmaengine_cfg, sizeof(*dmaengine_cfg));
/* Set ENBLn earlier to make sure dma request triggered after that */
- if (sdmac->event_id0) {
- if (sdmac->event_id0 >= sdmac->sdma->drvdata->num_events)
- return -EINVAL;
- sdma_event_enable(sdmac, sdmac->event_id0);
- }
+ if (sdmac->event_id0 >= sdmac->sdma->drvdata->num_events)
+ return -EINVAL;
+ sdma_event_enable(sdmac, sdmac->event_id0);
if (sdmac->event_id1) {
if (sdmac->event_id1 >= sdmac->sdma->drvdata->num_events)
--
2.7.4
^ permalink raw reply related
* [PATCH v1 10/15] dt-bindings: dma: imx-sdma: add i.mx6ul/6sx compatible name
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
Add i.mx6ul and i.mx6sx compatible name.
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt b/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
index 9d8bbac..d024a83 100644
--- a/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
+++ b/Documentation/devicetree/bindings/dma/fsl-imx-sdma.txt
@@ -9,6 +9,8 @@ Required properties:
"fsl,imx53-sdma"
"fsl,imx6q-sdma"
"fsl,imx7d-sdma"
+ "fsl,imx6sx-sdma"
+ "fsl,imx6ul-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
--
2.7.4
^ permalink raw reply related
* [PATCH v1 06/15] spi: imx: fix ERR009165
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
Change to XCH mode even in dma mode, please refer to the below
errata:
https://www.nxp.com/docs/en/errata/IMX6DQCE.pdf
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
drivers/spi/spi-imx.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c
index 09c9a1e..eb56eac 100644
--- a/drivers/spi/spi-imx.c
+++ b/drivers/spi/spi-imx.c
@@ -585,8 +585,9 @@ static int mx51_ecspi_prepare_transfer(struct spi_imx_data *spi_imx,
ctrl |= mx51_ecspi_clkdiv(spi_imx, t->speed_hz, &clk);
spi_imx->spi_bus_clk = clk;
+ /* ERR009165: work in XHC mode as PIO */
if (spi_imx->usedma)
- ctrl |= MX51_ECSPI_CTRL_SMC;
+ ctrl &= ~MX51_ECSPI_CTRL_SMC;
writel(ctrl, spi_imx->base + MX51_ECSPI_CTRL);
@@ -612,12 +613,14 @@ static int mx51_ecspi_prepare_transfer(struct spi_imx_data *spi_imx,
static void mx51_setup_wml(struct spi_imx_data *spi_imx)
{
+ u32 tx_wml = 0;
+
/*
* Configure the DMA register: setup the watermark
* and enable DMA request.
*/
writel(MX51_ECSPI_DMA_RX_WML(spi_imx->wml - 1) |
- MX51_ECSPI_DMA_TX_WML(spi_imx->wml) |
+ MX51_ECSPI_DMA_TX_WML(tx_wml) |
MX51_ECSPI_DMA_RXT_WML(spi_imx->wml) |
MX51_ECSPI_DMA_TEDEN | MX51_ECSPI_DMA_RXDEN |
MX51_ECSPI_DMA_RXTDEN, spi_imx->base + MX51_ECSPI_DMA);
@@ -1265,10 +1268,6 @@ static int spi_imx_sdma_init(struct device *dev, struct spi_imx_data *spi_imx,
{
int ret;
- /* use pio mode for i.mx6dl chip TKT238285 */
- if (of_machine_is_compatible("fsl,imx6dl"))
- return 0;
-
spi_imx->wml = spi_imx->devtype_data->fifo_size / 2;
/* Prepare for TX DMA: */
--
2.7.4
^ permalink raw reply related
* [PATCH v1 07/15] spi: imx: remove ERR009165 workaround on i.mx6ul
From: Robin Gong @ 2019-04-23 13:51 UTC (permalink / raw)
To: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
festevam@gmail.com, robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk
Cc: dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-1-git-send-email-yibin.gong@nxp.com>
ERR009165 fix on i.mx6ul and next chip, such as i.mx6ull/i.mx8mq/i.mx8mm.
Remove workaround on those chips. Add new i.mx6ul type for that.
Signed-off-by: Robin Gong <yibin.gong@nxp.com>
---
drivers/spi/spi-imx.c | 39 +++++++++++++++++++++++++++++++++++----
1 file changed, 35 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c
index eb56eac..2e5e978 100644
--- a/drivers/spi/spi-imx.c
+++ b/drivers/spi/spi-imx.c
@@ -57,6 +57,7 @@ enum spi_imx_devtype {
IMX35_CSPI, /* CSPI on all i.mx except above */
IMX51_ECSPI, /* ECSPI on i.mx51 */
IMX53_ECSPI, /* ECSPI on i.mx53 and later */
+ IMX6UL_ECSPI, /* ERR009165 fix from i.mx6ul */
};
struct spi_imx_data;
@@ -128,7 +129,8 @@ static inline int is_imx35_cspi(struct spi_imx_data *d)
static inline int is_imx51_ecspi(struct spi_imx_data *d)
{
- return d->devtype_data->devtype == IMX51_ECSPI;
+ return d->devtype_data->devtype == IMX51_ECSPI ||
+ d->devtype_data->devtype == IMX6UL_ECSPI;
}
static inline int is_imx53_ecspi(struct spi_imx_data *d)
@@ -585,9 +587,16 @@ static int mx51_ecspi_prepare_transfer(struct spi_imx_data *spi_imx,
ctrl |= mx51_ecspi_clkdiv(spi_imx, t->speed_hz, &clk);
spi_imx->spi_bus_clk = clk;
- /* ERR009165: work in XHC mode as PIO */
- if (spi_imx->usedma)
- ctrl &= ~MX51_ECSPI_CTRL_SMC;
+ /*
+ * ERR009165: work in XHC mode instead of SMC as PIO on the chips
+ * before i.mx6ul.
+ */
+ if (spi_imx->usedma) {
+ if (spi_imx->devtype_data->devtype == IMX6UL_ECSPI)
+ ctrl |= MX51_ECSPI_CTRL_SMC;
+ else
+ ctrl &= ~MX51_ECSPI_CTRL_SMC;
+ }
writel(ctrl, spi_imx->base + MX51_ECSPI_CTRL);
@@ -615,6 +624,8 @@ static void mx51_setup_wml(struct spi_imx_data *spi_imx)
{
u32 tx_wml = 0;
+ if (spi_imx->devtype_data->devtype == IMX6UL_ECSPI)
+ tx_wml = spi_imx->wml;
/*
* Configure the DMA register: setup the watermark
* and enable DMA request.
@@ -1012,6 +1023,22 @@ static struct spi_imx_devtype_data imx53_ecspi_devtype_data = {
.devtype = IMX53_ECSPI,
};
+static struct spi_imx_devtype_data imx6ul_ecspi_devtype_data = {
+ .intctrl = mx51_ecspi_intctrl,
+ .prepare_message = mx51_ecspi_prepare_message,
+ .prepare_transfer = mx51_ecspi_prepare_transfer,
+ .trigger = mx51_ecspi_trigger,
+ .rx_available = mx51_ecspi_rx_available,
+ .reset = mx51_ecspi_reset,
+ .setup_wml = mx51_setup_wml,
+ .fifo_size = 64,
+ .has_dmamode = true,
+ .dynamic_burst = true,
+ .has_slavemode = true,
+ .disable = mx51_ecspi_disable,
+ .devtype = IMX6UL_ECSPI,
+};
+
static const struct platform_device_id spi_imx_devtype[] = {
{
.name = "imx1-cspi",
@@ -1035,6 +1062,9 @@ static const struct platform_device_id spi_imx_devtype[] = {
.name = "imx53-ecspi",
.driver_data = (kernel_ulong_t) &imx53_ecspi_devtype_data,
}, {
+ .name = "imx6ul-ecspi",
+ .driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,
+ }, {
/* sentinel */
}
};
@@ -1047,6 +1077,7 @@ static const struct of_device_id spi_imx_dt_ids[] = {
{ .compatible = "fsl,imx35-cspi", .data = &imx35_cspi_devtype_data, },
{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },
{ .compatible = "fsl,imx53-ecspi", .data = &imx53_ecspi_devtype_data, },
+ { .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
--
2.7.4
^ permalink raw reply related
* [v1,01/15] Revert "ARM: dts: imx6q: Use correct SDMA script for SPI5 core"
From: Fabio Estevam @ 2019-04-23 14:02 UTC (permalink / raw)
To: Robin Gong
Cc: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk, dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
Hi Robin,
On Tue, Apr 23, 2019 at 10:50 AM Robin Gong <yibin.gong@nxp.com> wrote:
>
> This reverts commit df07101e1c4a29e820df02f9989a066988b160e6.
You need to provide a detailed explanation in the commit log as to why
the revert is needed.
^ permalink raw reply
* Re: [PATCH v1 01/15] Revert "ARM: dts: imx6q: Use correct SDMA script for SPI5 core"
From: Fabio Estevam @ 2019-04-23 14:02 UTC (permalink / raw)
To: Robin Gong
Cc: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk, dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-2-git-send-email-yibin.gong@nxp.com>
Hi Robin,
On Tue, Apr 23, 2019 at 10:50 AM Robin Gong <yibin.gong@nxp.com> wrote:
>
> This reverts commit df07101e1c4a29e820df02f9989a066988b160e6.
You need to provide a detailed explanation in the commit log as to why
the revert is needed.
^ permalink raw reply
* [v1,02/15] Revert "ARM: dts: imx6: Use correct SDMA script for SPI cores"
From: Fabio Estevam @ 2019-04-23 14:03 UTC (permalink / raw)
To: Robin Gong
Cc: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk, dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
On Tue, Apr 23, 2019 at 10:50 AM Robin Gong <yibin.gong@nxp.com> wrote:
>
> This reverts commit dd4b487b32a3571fdcc66062e661e3a3e360e35b.
Same here.
^ permalink raw reply
* Re: [PATCH v1 02/15] Revert "ARM: dts: imx6: Use correct SDMA script for SPI cores"
From: Fabio Estevam @ 2019-04-23 14:03 UTC (permalink / raw)
To: Robin Gong
Cc: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk, dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-3-git-send-email-yibin.gong@nxp.com>
On Tue, Apr 23, 2019 at 10:50 AM Robin Gong <yibin.gong@nxp.com> wrote:
>
> This reverts commit dd4b487b32a3571fdcc66062e661e3a3e360e35b.
Same here.
^ permalink raw reply
* [v1,09/15] dmaengine: imx-sdma: remove ERR009165 on i.mx6ul
From: Fabio Estevam @ 2019-04-23 14:12 UTC (permalink / raw)
To: Robin Gong
Cc: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk, dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
On Tue, Apr 23, 2019 at 10:51 AM Robin Gong <yibin.gong@nxp.com> wrote:
>
> ECSPI issue fixed from i.mx6ul at hardware level, no need
> ERR009165 anymore on those chips such as i.mx8mq. Add i.mx6sx
> from where i.mx6ul source.
>
> Signed-off-by: Robin Gong <yibin.gong@nxp.com>
> ---
> drivers/dma/imx-sdma.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 42 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
> index 1e20470..99c44a5 100644
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -419,6 +419,7 @@ struct sdma_driver_data {
> int num_events;
> struct sdma_script_start_addrs *script_addrs;
> bool check_ratio;
> + bool ecspi_fixed;
Please state the erratum number in the variable name, such as
has_err009165_fixed
^ permalink raw reply
* Re: [PATCH v1 09/15] dmaengine: imx-sdma: remove ERR009165 on i.mx6ul
From: Fabio Estevam @ 2019-04-23 14:12 UTC (permalink / raw)
To: Robin Gong
Cc: broonie@kernel.org, shawnguo@kernel.org, s.hauer@pengutronix.de,
robh+dt@kernel.org, mark.rutland@arm.com,
u.kleine-koenig@pengutronix.de, plyatov@gmail.com,
sean.nyekjaer@prevas.dk, dl-linux-imx, linux-spi@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, dmaengine@vger.kernel.org,
devicetree@vger.kernel.org, kernel@pengutronix.de
In-Reply-To: <1556027045-5269-10-git-send-email-yibin.gong@nxp.com>
On Tue, Apr 23, 2019 at 10:51 AM Robin Gong <yibin.gong@nxp.com> wrote:
>
> ECSPI issue fixed from i.mx6ul at hardware level, no need
> ERR009165 anymore on those chips such as i.mx8mq. Add i.mx6sx
> from where i.mx6ul source.
>
> Signed-off-by: Robin Gong <yibin.gong@nxp.com>
> ---
> drivers/dma/imx-sdma.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 42 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
> index 1e20470..99c44a5 100644
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -419,6 +419,7 @@ struct sdma_driver_data {
> int num_events;
> struct sdma_script_start_addrs *script_addrs;
> bool check_ratio;
> + bool ecspi_fixed;
Please state the erratum number in the variable name, such as
has_err009165_fixed
^ permalink raw reply
* dmaengine: stm32-dma: fix residue calculation in stm32-dma
From: Arnaud Pouliquen @ 2019-04-23 15:18 UTC (permalink / raw)
To: Vinod Koul
Cc: Dan Williams, Pierre-Yves MORDRET, linux-stm32, linux-kernel,
dmaengine
Hello Vinod,
Just a gentle reminder, if you could take a moment to review this patch.
FYI, the patch has already been internally reviewed by Pierre Yves
mordret...
His sign-off is in the commit message.
Thanks,
Arnaud
On 3/27/19 1:21 PM, Arnaud Pouliquen wrote:
> During residue calculation. the DMA can switch to the next sg. When
> this race condition occurs, the residue returned value is not valid.
> Indeed the position in the sg returned by the hardware is the position
> of the next sg, not the current sg.
> Solution is to check the sg after the calculation to verify it.
> If a transition is detected we consider that the DMA has switched to
> the beginning of next sg.
>
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
> Signed-off-by: Pierre-Yves MORDRET <pierre-yves.mordret@st.com>
> ---
> drivers/dma/stm32-dma.c | 70 ++++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 57 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/dma/stm32-dma.c b/drivers/dma/stm32-dma.c
> index 4903a40..30309d2 100644
> --- a/drivers/dma/stm32-dma.c
> +++ b/drivers/dma/stm32-dma.c
> @@ -1038,33 +1038,77 @@ static u32 stm32_dma_get_remaining_bytes(struct stm32_dma_chan *chan)
> return ndtr << width;
> }
>
> +static bool stm32_dma_is_current_sg(struct stm32_dma_chan *chan)
> +{
> + struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan);
> + struct stm32_dma_sg_req *sg_req;
> + u32 dma_scr, dma_smar, id;
> +
> + id = chan->id;
> + dma_scr = stm32_dma_read(dmadev, STM32_DMA_SCR(id));
> +
> + if (!(dma_scr & STM32_DMA_SCR_DBM))
> + return true;
> +
> + sg_req = &chan->desc->sg_req[chan->next_sg];
> +
> + if (dma_scr & STM32_DMA_SCR_CT) {
> + dma_smar = stm32_dma_read(dmadev, STM32_DMA_SM0AR(id));
> + return (dma_smar == sg_req->chan_reg.dma_sm0ar);
> + }
> +
> + dma_smar = stm32_dma_read(dmadev, STM32_DMA_SM1AR(id));
> +
> + return (dma_smar == sg_req->chan_reg.dma_sm1ar);
> +}
> +
> static size_t stm32_dma_desc_residue(struct stm32_dma_chan *chan,
> struct stm32_dma_desc *desc,
> u32 next_sg)
> {
> u32 modulo, burst_size;
> - u32 residue = 0;
> + u32 residue;
> + u32 n_sg = next_sg;
> + struct stm32_dma_sg_req *sg_req = &chan->desc->sg_req[chan->next_sg];
> int i;
>
> + residue = stm32_dma_get_remaining_bytes(chan);
> +
> /*
> - * In cyclic mode, for the last period, residue = remaining bytes from
> - * NDTR
> + * Calculate the residue means compute the descriptors
> + * information:
> + * - the sg currently transferred
> + * - the remaining position in this sg (NDTR).
> + *
> + * The issue is that a race condition can occur if DMA is
> + * running. DMA can have started to transfer the next sg before
> + * the position in sg is read. In this case the remaing position
> + * can correspond to the new sg position.
> + * The strategy implemented in the stm32 driver is to check the
> + * sg transition. If detected we can not trust the SxNDTR register
> + * value, this register can not be up to date during the transition.
> + * In this case we can assume that the dma is at the beginning of next
> + * sg so we calculate the residue in consequence.
> */
> - if (chan->desc->cyclic && next_sg == 0) {
> - residue = stm32_dma_get_remaining_bytes(chan);
> - goto end;
> +
> + if (!stm32_dma_is_current_sg(chan)) {
> + n_sg++;
> + if (n_sg == chan->desc->num_sgs)
> + n_sg = 0;
> + residue = sg_req->len;
> }
>
> /*
> - * For all other periods in cyclic mode, and in sg mode,
> - * residue = remaining bytes from NDTR + remaining periods/sg to be
> - * transferred
> + * In cyclic mode, for the last period, residue = remaining bytes
> + * from NDTR,
> + * else for all other periods in cyclic mode, and in sg mode,
> + * residue = remaining bytes from NDTR + remaining
> + * periods/sg to be transferred
> */
> - for (i = next_sg; i < desc->num_sgs; i++)
> - residue += desc->sg_req[i].len;
> - residue += stm32_dma_get_remaining_bytes(chan);
> + if (!chan->desc->cyclic || n_sg != 0)
> + for (i = n_sg; i < desc->num_sgs; i++)
> + residue += desc->sg_req[i].len;
>
> -end:
> if (!chan->mem_burst)
> return residue;
>
>
^ permalink raw reply
* Re: [PATCH] dmaengine: stm32-dma: fix residue calculation in stm32-dma
From: Arnaud Pouliquen @ 2019-04-23 15:18 UTC (permalink / raw)
To: Vinod Koul
Cc: Dan Williams, Pierre-Yves MORDRET, linux-stm32, linux-kernel,
dmaengine
In-Reply-To: <1553689316-6231-1-git-send-email-arnaud.pouliquen@st.com>
Hello Vinod,
Just a gentle reminder, if you could take a moment to review this patch.
FYI, the patch has already been internally reviewed by Pierre Yves
mordret...
His sign-off is in the commit message.
Thanks,
Arnaud
On 3/27/19 1:21 PM, Arnaud Pouliquen wrote:
> During residue calculation. the DMA can switch to the next sg. When
> this race condition occurs, the residue returned value is not valid.
> Indeed the position in the sg returned by the hardware is the position
> of the next sg, not the current sg.
> Solution is to check the sg after the calculation to verify it.
> If a transition is detected we consider that the DMA has switched to
> the beginning of next sg.
>
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
> Signed-off-by: Pierre-Yves MORDRET <pierre-yves.mordret@st.com>
> ---
> drivers/dma/stm32-dma.c | 70 ++++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 57 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/dma/stm32-dma.c b/drivers/dma/stm32-dma.c
> index 4903a40..30309d2 100644
> --- a/drivers/dma/stm32-dma.c
> +++ b/drivers/dma/stm32-dma.c
> @@ -1038,33 +1038,77 @@ static u32 stm32_dma_get_remaining_bytes(struct stm32_dma_chan *chan)
> return ndtr << width;
> }
>
> +static bool stm32_dma_is_current_sg(struct stm32_dma_chan *chan)
> +{
> + struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan);
> + struct stm32_dma_sg_req *sg_req;
> + u32 dma_scr, dma_smar, id;
> +
> + id = chan->id;
> + dma_scr = stm32_dma_read(dmadev, STM32_DMA_SCR(id));
> +
> + if (!(dma_scr & STM32_DMA_SCR_DBM))
> + return true;
> +
> + sg_req = &chan->desc->sg_req[chan->next_sg];
> +
> + if (dma_scr & STM32_DMA_SCR_CT) {
> + dma_smar = stm32_dma_read(dmadev, STM32_DMA_SM0AR(id));
> + return (dma_smar == sg_req->chan_reg.dma_sm0ar);
> + }
> +
> + dma_smar = stm32_dma_read(dmadev, STM32_DMA_SM1AR(id));
> +
> + return (dma_smar == sg_req->chan_reg.dma_sm1ar);
> +}
> +
> static size_t stm32_dma_desc_residue(struct stm32_dma_chan *chan,
> struct stm32_dma_desc *desc,
> u32 next_sg)
> {
> u32 modulo, burst_size;
> - u32 residue = 0;
> + u32 residue;
> + u32 n_sg = next_sg;
> + struct stm32_dma_sg_req *sg_req = &chan->desc->sg_req[chan->next_sg];
> int i;
>
> + residue = stm32_dma_get_remaining_bytes(chan);
> +
> /*
> - * In cyclic mode, for the last period, residue = remaining bytes from
> - * NDTR
> + * Calculate the residue means compute the descriptors
> + * information:
> + * - the sg currently transferred
> + * - the remaining position in this sg (NDTR).
> + *
> + * The issue is that a race condition can occur if DMA is
> + * running. DMA can have started to transfer the next sg before
> + * the position in sg is read. In this case the remaing position
> + * can correspond to the new sg position.
> + * The strategy implemented in the stm32 driver is to check the
> + * sg transition. If detected we can not trust the SxNDTR register
> + * value, this register can not be up to date during the transition.
> + * In this case we can assume that the dma is at the beginning of next
> + * sg so we calculate the residue in consequence.
> */
> - if (chan->desc->cyclic && next_sg == 0) {
> - residue = stm32_dma_get_remaining_bytes(chan);
> - goto end;
> +
> + if (!stm32_dma_is_current_sg(chan)) {
> + n_sg++;
> + if (n_sg == chan->desc->num_sgs)
> + n_sg = 0;
> + residue = sg_req->len;
> }
>
> /*
> - * For all other periods in cyclic mode, and in sg mode,
> - * residue = remaining bytes from NDTR + remaining periods/sg to be
> - * transferred
> + * In cyclic mode, for the last period, residue = remaining bytes
> + * from NDTR,
> + * else for all other periods in cyclic mode, and in sg mode,
> + * residue = remaining bytes from NDTR + remaining
> + * periods/sg to be transferred
> */
> - for (i = next_sg; i < desc->num_sgs; i++)
> - residue += desc->sg_req[i].len;
> - residue += stm32_dma_get_remaining_bytes(chan);
> + if (!chan->desc->cyclic || n_sg != 0)
> + for (i = n_sg; i < desc->num_sgs; i++)
> + residue += desc->sg_req[i].len;
>
> -end:
> if (!chan->mem_burst)
> return residue;
>
>
^ permalink raw reply
* [RFC,v6,1/6] dmaengine: Add Synopsys eDMA IP core driver
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Vinod Koul, Dan Williams, Andy Shevchenko,
Russell King, Joao Pinto
Add Synopsys PCIe Endpoint eDMA IP core driver to kernel.
This IP is generally distributed with Synopsys PCIe Endpoint IP (depends
of the use and licensing agreement).
This core driver, initializes and configures the eDMA IP using vma-helpers
functions and dma-engine subsystem.
This driver can be compile as built-in or external module in kernel.
To enable this driver just select DW_EDMA option in kernel configuration,
however it requires and selects automatically DMA_ENGINE and
DMA_VIRTUAL_CHANNELS option too.
In order to transfer data from point A to B as fast as possible this IP
requires a dedicated memory space containing linked list of elements.
All elements of this linked list are continuous and each one describes a
data transfer (source and destination addresses, length and a control
variable).
For the sake of simplicity, lets assume a memory space for channel write
0 which allows about 42 elements.
+---------+
| Desc #0 |-+
+---------+ |
V
+----------+
| Chunk #0 |-+
| CB = 1 | | +----------+ +-----+ +-----------+ +-----+
+----------+ +->| Burst #0 |->| ... |->| Burst #41 |->| llp |
| +----------+ +-----+ +-----------+ +-----+
V
+----------+
| Chunk #1 |-+
| CB = 0 | | +-----------+ +-----+ +-----------+ +-----+
+----------+ +->| Burst #42 |->| ... |->| Burst #83 |->| llp |
| +-----------+ +-----+ +-----------+ +-----+
V
+----------+
| Chunk #2 |-+
| CB = 1 | | +-----------+ +-----+ +------------+ +-----+
+----------+ +->| Burst #84 |->| ... |->| Burst #125 |->| llp |
| +-----------+ +-----+ +------------+ +-----+
V
+----------+
| Chunk #3 |-+
| CB = 0 | | +------------+ +-----+ +------------+ +-----+
+----------+ +->| Burst #126 |->| ... |->| Burst #129 |->| llp |
+------------+ +-----+ +------------+ +-----+
Legend:
- Linked list, also know as Chunk
- Linked list element*, also know as Burst *CB*, also know as Change Bit,
it's a control bit (and typically is toggled) that allows to easily
identify and differentiate between the current linked list and the
previous or the next one.
- LLP, is a special element that indicates the end of the linked list
element stream also informs that the next CB should be toggle
On every last Burst of the Chunk (Burst #41, Burst #83, Burst #125 or
even Burst #129) is set some flags on their control variable (RIE and
LIE bits) that will trigger the send of "done" interruption.
On the interruptions callback, is decided whether to recycle the linked
list memory space by writing a new set of Bursts elements (if still
exists Chunks to transfer) or is considered completed (if there is no
Chunks available to transfer).
On scatter-gather transfer mode, the client will submit a scatter-gather
list of n (on this case 130) elements, that will be divide in multiple
Chunks, each Chunk will have (on this case 42) a limited number of
Bursts and after transferring all Bursts, an interrupt will be
triggered, which will allow to recycle the all linked list dedicated
memory again with the new information relative to the next Chunk and
respective Burst associated and repeat the whole cycle again.
On cyclic transfer mode, the client will submit a buffer pointer, length
of it and number of repetitions, in this case each burst will correspond
directly to each repetition.
Each Burst can describes a data transfer from point A(source) to point
B(destination) with a length that can be from 1 byte up to 4 GB. Since
dedicated the memory space where the linked list will reside is limited,
the whole n burst elements will be organized in several Chunks, that
will be used later to recycle the dedicated memory space to initiate a
new sequence of data transfers.
The whole transfer is considered has completed when it was transferred
all bursts.
Currently this IP has a set well-known register map, which includes
support for legacy and unroll modes. Legacy mode is version of this
register map that has multiplexer register that allows to switch
registers between all write and read channels and the unroll modes
repeats all write and read channels registers with an offset between
them. This register map is called v0.
The IP team is creating a new register map more suitable to the latest
PCIe features, that very likely will change the map register, which this
version will be called v1. As soon as this new version is released by
the IP team the support for this version in be included on this driver.
This patch has a direct dependency of the patch ("[RFC 2/7] dmaengine:
Add Synopsys eDMA IP version 0 support").
According to the logic, patches 1, 2 and 3 should be squashed into 1
unique patch, but for the sake of simplicity of review, it was divided
in this 3 patches files.
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Russell King <rmk+kernel@armlinux.org.uk>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- Replace comments // (C99 style) by /**/
- Fix the headers of the .c and .h files according to the most recent
convention
- Fix errors and checks pointed out by checkpatch with --strict option
- Replace patch small description tag from dma by dmaengine
- Change some dev_info() into dev_dbg()
- Remove unnecessary zero initialization after kzalloc
- Remove direction validation on config() API, since the direction
parameter is deprecated
- Refactor code to replace atomic_t by u32 variable type
- Replace start_transfer() name by dw_edma_start_transfer()
- Add spinlock to dw_edma_device_prep_slave_sg()
- Add spinlock to dw_edma_free_chunk()
- Simplify switch case into if on dw_edma_device_pause(),
dw_edma_device_resume() and dw_edma_device_terminate_all()
RFC v2->RFC v3:
- Add driver parameter to disable msix feature
- Fix printk variable of phys_addr_t type
- Fix printk variable of __iomem type
- Fix printk variable of size_t type
- Add comments or improve existing ones
- Add possibility to work with multiple IRQs feature
- Fix source and destination addresses
- Add define to magic numbers
- Add DMA cyclic transfer feature
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- Remove unnecessary dev_info() calls
- Add multiple IRQ automatic adaption feature
- Reorder variables declaration in reverse tree order on several
functions
- Add return check of dw_edma_alloc_burst() in dw_edma_alloc_chunk()
- Add return check of dw_edma_alloc_chunk() in dw_edma_alloc_desc()
- Remove pm_runtime_get_sync() call in probe()
- Fix dma_cyclic() buffer address
- Replace devm_*_irq() by *_irq() and recode accordingly
- Fix license header
- Replace kvzalloc() by kzalloc() and kvfree() by kfree()
- Move ops->device_config callback from config() to probe()
- Remove restriction to perform operation only in IDLE state on
dw_edma_device_config(), dw_edma_device_prep_slave_sg(),
dw_edma_device_prep_dma_cyclic()
- Recode and simplify slave_sg() and dma_cyclic()
- Recode and simplify interrupts and channel setup
- Recode dw_edma_device_tx_status()
- Move get_cached_msi_msg() to here
- Code rewrite to use direct dw-edma-v0-core functions instead of
callbacks
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Add author on file header
- Remove debug prints
- Code rewrite following Andy's suggestations
drivers/dma/Kconfig | 2 +
drivers/dma/Makefile | 1 +
drivers/dma/dw-edma/Kconfig | 9 +
drivers/dma/dw-edma/Makefile | 4 +
drivers/dma/dw-edma/dw-edma-core.c | 919 +++++++++++++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-core.h | 165 +++++++
include/linux/dma/edma.h | 47 ++
7 files changed, 1147 insertions(+)
create mode 100644 drivers/dma/dw-edma/Kconfig
create mode 100644 drivers/dma/dw-edma/Makefile
create mode 100644 drivers/dma/dw-edma/dw-edma-core.c
create mode 100644 drivers/dma/dw-edma/dw-edma-core.h
create mode 100644 include/linux/dma/edma.h
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 0b1dfb5..58b78d2 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -665,6 +665,8 @@ source "drivers/dma/qcom/Kconfig"
source "drivers/dma/dw/Kconfig"
+source "drivers/dma/dw-edma/Kconfig"
+
source "drivers/dma/hsu/Kconfig"
source "drivers/dma/sh/Kconfig"
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 6126e1c..5bddf6f 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_DMA_SUN4I) += sun4i-dma.o
obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o
obj-$(CONFIG_DW_AXI_DMAC) += dw-axi-dmac/
obj-$(CONFIG_DW_DMAC_CORE) += dw/
+obj-$(CONFIG_DW_EDMA) += dw-edma/
obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o
obj-$(CONFIG_FSL_DMA) += fsldma.o
obj-$(CONFIG_FSL_EDMA) += fsl-edma.o fsl-edma-common.o
diff --git a/drivers/dma/dw-edma/Kconfig b/drivers/dma/dw-edma/Kconfig
new file mode 100644
index 0000000..3016bed
--- /dev/null
+++ b/drivers/dma/dw-edma/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config DW_EDMA
+ tristate "Synopsys DesignWare eDMA controller driver"
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ Support the Synopsys DesignWare eDMA controller, normally
+ implemented on endpoints SoCs.
diff --git a/drivers/dma/dw-edma/Makefile b/drivers/dma/dw-edma/Makefile
new file mode 100644
index 0000000..3224010
--- /dev/null
+++ b/drivers/dma/dw-edma/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_DW_EDMA) += dw-edma.o
+dw-edma-objs := dw-edma-core.o
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
new file mode 100644
index 0000000..54cd531
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA core driver
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/dmaengine.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/dma/edma.h>
+#include <linux/pci.h>
+
+#include "dw-edma-core.h"
+#include "../dmaengine.h"
+#include "../virt-dma.h"
+
+static inline
+struct device *dchan2dev(struct dma_chan *dchan)
+{
+ return &dchan->dev->device;
+}
+
+static inline
+struct device *chan2dev(struct dw_edma_chan *chan)
+{
+ return &chan->vc.chan.dev->device;
+}
+
+static inline
+struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd)
+{
+ return container_of(vd, struct dw_edma_desc, vd);
+}
+
+static struct dw_edma_burst *dw_edma_alloc_burst(struct dw_edma_chunk *chunk)
+{
+ struct dw_edma_burst *burst;
+
+ burst = kzalloc(sizeof(*burst), GFP_KERNEL);
+ if (unlikely(!burst))
+ return NULL;
+
+ INIT_LIST_HEAD(&burst->list);
+ if (chunk->burst) {
+ /* Create and add new element into the linked list */
+ chunk->bursts_alloc++;
+ list_add_tail(&burst->list, &chunk->burst->list);
+ } else {
+ /* List head */
+ chunk->bursts_alloc = 0;
+ chunk->burst = burst;
+ }
+
+ return burst;
+}
+
+static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc)
+{
+ struct dw_edma_chan *chan = desc->chan;
+ struct dw_edma *dw = chan->chip->dw;
+ struct dw_edma_chunk *chunk;
+
+ chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
+ if (unlikely(!chunk))
+ return NULL;
+
+ INIT_LIST_HEAD(&chunk->list);
+ chunk->chan = chan;
+ chunk->cb = !(desc->chunks_alloc % 2);
+ chunk->ll_region.paddr = dw->ll_region.paddr + chan->ll_off;
+ chunk->ll_region.vaddr = dw->ll_region.vaddr + chan->ll_off;
+
+ if (desc->chunk) {
+ /* Create and add new element into the linked list */
+ desc->chunks_alloc++;
+ list_add_tail(&chunk->list, &desc->chunk->list);
+ if (!dw_edma_alloc_burst(chunk)) {
+ kfree(chunk);
+ return NULL;
+ }
+ } else {
+ /* List head */
+ chunk->burst = NULL;
+ desc->chunks_alloc = 0;
+ desc->chunk = chunk;
+ }
+
+ return chunk;
+}
+
+static struct dw_edma_desc *dw_edma_alloc_desc(struct dw_edma_chan *chan)
+{
+ struct dw_edma_desc *desc;
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (unlikely(!desc))
+ return NULL;
+
+ desc->chan = chan;
+ if (!dw_edma_alloc_chunk(desc)) {
+ kfree(desc);
+ return NULL;
+ }
+
+ return desc;
+}
+
+static void dw_edma_free_burst(struct dw_edma_chunk *chunk)
+{
+ struct dw_edma_burst *child, *_next;
+
+ /* Remove all the list elements */
+ list_for_each_entry_safe(child, _next, &chunk->burst->list, list) {
+ list_del(&child->list);
+ kfree(child);
+ chunk->bursts_alloc--;
+ }
+
+ /* Remove the list head */
+ kfree(child);
+ chunk->burst = NULL;
+}
+
+static void dw_edma_free_chunk(struct dw_edma_desc *desc)
+{
+ struct dw_edma_chunk *child, *_next;
+
+ if (!desc->chunk)
+ return;
+
+ /* Remove all the list elements */
+ list_for_each_entry_safe(child, _next, &desc->chunk->list, list) {
+ dw_edma_free_burst(child);
+ list_del(&child->list);
+ kfree(child);
+ desc->chunks_alloc--;
+ }
+
+ /* Remove the list head */
+ kfree(child);
+ desc->chunk = NULL;
+}
+
+static void dw_edma_free_desc(struct dw_edma_desc *desc)
+{
+ dw_edma_free_chunk(desc);
+ kfree(desc);
+}
+
+static void vchan_free_desc(struct virt_dma_desc *vdesc)
+{
+ dw_edma_free_desc(vd2dw_edma_desc(vdesc));
+}
+
+static void dw_edma_start_transfer(struct dw_edma_chan *chan)
+{
+ struct dw_edma_chunk *child;
+ struct dw_edma_desc *desc;
+ struct virt_dma_desc *vd;
+
+ vd = vchan_next_desc(&chan->vc);
+ if (!vd)
+ return;
+
+ desc = vd2dw_edma_desc(vd);
+ if (!desc)
+ return;
+
+ child = list_first_entry_or_null(&desc->chunk->list,
+ struct dw_edma_chunk, list);
+ if (!child)
+ return;
+
+ dw_edma_v0_core_start(child, !desc->xfer_sz);
+ desc->xfer_sz += child->ll_region.sz;
+ dw_edma_free_burst(child);
+ list_del(&child->list);
+ kfree(child);
+ desc->chunks_alloc--;
+}
+
+static int dw_edma_device_config(struct dma_chan *dchan,
+ struct dma_slave_config *config)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ memcpy(&chan->config, config, sizeof(*config));
+ chan->configured = true;
+
+ return 0;
+}
+
+static int dw_edma_device_pause(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int err = 0;
+
+ if (!chan->configured)
+ err = -EPERM;
+ else if (chan->status != EDMA_ST_BUSY)
+ err = -EPERM;
+ else if (chan->request != EDMA_REQ_NONE)
+ err = -EPERM;
+ else
+ chan->request = EDMA_REQ_PAUSE;
+
+ return err;
+}
+
+static int dw_edma_device_resume(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int err = 0;
+
+ if (!chan->configured) {
+ err = -EPERM;
+ } else if (chan->status != EDMA_ST_PAUSE) {
+ err = -EPERM;
+ } else if (chan->request != EDMA_REQ_NONE) {
+ err = -EPERM;
+ } else {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ }
+
+ return err;
+}
+
+static int dw_edma_device_terminate_all(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int err = 0;
+ LIST_HEAD(head);
+
+ if (!chan->configured) {
+ /* Do nothing */
+ } else if (chan->status == EDMA_ST_PAUSE) {
+ chan->status = EDMA_ST_IDLE;
+ chan->configured = false;
+ } else if (chan->status == EDMA_ST_IDLE) {
+ chan->configured = false;
+ } else if (dw_edma_v0_core_ch_status(chan) == DMA_COMPLETE) {
+ /*
+ * The channel is in a false BUSY state, probably didn't
+ * receive or lost an interrupt
+ */
+ chan->status = EDMA_ST_IDLE;
+ chan->configured = false;
+ } else if (chan->request > EDMA_REQ_PAUSE) {
+ err = -EPERM;
+ } else {
+ chan->request = EDMA_REQ_STOP;
+ }
+
+ return err;
+}
+
+static void dw_edma_device_issue_pending(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ if (chan->configured && chan->request == EDMA_REQ_NONE &&
+ chan->status == EDMA_ST_IDLE && vchan_issue_pending(&chan->vc)) {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+}
+
+static enum dma_status
+dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
+ struct dma_tx_state *txstate)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ struct dw_edma_desc *desc;
+ struct virt_dma_desc *vd;
+ unsigned long flags;
+ enum dma_status ret;
+ u32 residue = 0;
+
+ ret = dma_cookie_status(dchan, cookie, txstate);
+ if (ret == DMA_COMPLETE)
+ return ret;
+
+ if (ret == DMA_IN_PROGRESS && chan->status == EDMA_ST_PAUSE)
+ ret = DMA_PAUSED;
+
+ if (!txstate)
+ goto ret_residue;
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ vd = vchan_find_desc(&chan->vc, cookie);
+ if (vd) {
+ desc = vd2dw_edma_desc(vd);
+ if (desc)
+ residue = desc->alloc_sz - desc->xfer_sz;
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+
+ret_residue:
+ dma_set_residue(txstate, residue);
+
+ return ret;
+}
+
+static struct dma_async_tx_descriptor *
+dw_edma_device_transfer(struct dw_edma_transfer *xfer)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan);
+ enum dma_transfer_direction direction = xfer->direction;
+ phys_addr_t src_addr, dst_addr;
+ struct scatterlist *sg = NULL;
+ struct dw_edma_chunk *chunk;
+ struct dw_edma_burst *burst;
+ struct dw_edma_desc *desc;
+ u32 cnt;
+ int i;
+
+ if ((direction == DMA_MEM_TO_DEV && chan->dir == EDMA_DIR_WRITE) ||
+ (direction == DMA_DEV_TO_MEM && chan->dir == EDMA_DIR_READ))
+ return NULL;
+
+ if (xfer->cyclic) {
+ if (!xfer->xfer.cyclic.len || !xfer->xfer.cyclic.cnt)
+ return NULL;
+ } else {
+ if (xfer->xfer.sg.len < 1)
+ return NULL;
+ }
+
+ if (!chan->configured)
+ return NULL;
+
+ desc = dw_edma_alloc_desc(chan);
+ if (unlikely(!desc))
+ goto err_alloc;
+
+ chunk = dw_edma_alloc_chunk(desc);
+ if (unlikely(!chunk))
+ goto err_alloc;
+
+ src_addr = chan->config.src_addr;
+ dst_addr = chan->config.dst_addr;
+
+ if (xfer->cyclic) {
+ cnt = xfer->xfer.cyclic.cnt;
+ } else {
+ cnt = xfer->xfer.sg.len;
+ sg = xfer->xfer.sg.sgl;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ if (!xfer->cyclic && !sg)
+ break;
+
+ if (chunk->bursts_alloc == chan->ll_max) {
+ chunk = dw_edma_alloc_chunk(desc);
+ if (unlikely(!chunk))
+ goto err_alloc;
+ }
+
+ burst = dw_edma_alloc_burst(chunk);
+ if (unlikely(!burst))
+ goto err_alloc;
+
+ if (xfer->cyclic)
+ burst->sz = xfer->xfer.cyclic.len;
+ else
+ burst->sz = sg_dma_len(sg);
+
+ chunk->ll_region.sz += burst->sz;
+ desc->alloc_sz += burst->sz;
+
+ if (direction == DMA_DEV_TO_MEM) {
+ burst->sar = src_addr;
+ if (xfer->cyclic) {
+ burst->dar = xfer->xfer.cyclic.paddr;
+ } else {
+ burst->dar = sg_dma_address(sg);
+ src_addr += sg_dma_len(sg);
+ }
+ } else {
+ burst->dar = dst_addr;
+ if (xfer->cyclic) {
+ burst->sar = xfer->xfer.cyclic.paddr;
+ } else {
+ burst->sar = sg_dma_address(sg);
+ dst_addr += sg_dma_len(sg);
+ }
+ }
+
+ if (!xfer->cyclic)
+ sg = sg_next(sg);
+ }
+
+ return vchan_tx_prep(&chan->vc, &desc->vd, xfer->flags);
+
+err_alloc:
+ if (desc)
+ dw_edma_free_desc(desc);
+
+ return NULL;
+}
+
+static struct dma_async_tx_descriptor *
+dw_edma_device_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
+ unsigned int len,
+ enum dma_transfer_direction direction,
+ unsigned long flags, void *context)
+{
+ struct dw_edma_transfer xfer;
+
+ xfer.dchan = dchan;
+ xfer.direction = direction;
+ xfer.xfer.sg.sgl = sgl;
+ xfer.xfer.sg.len = len;
+ xfer.flags = flags;
+ xfer.cyclic = false;
+
+ return dw_edma_device_transfer(&xfer);
+}
+
+static struct dma_async_tx_descriptor *
+dw_edma_device_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t paddr,
+ size_t len, size_t count,
+ enum dma_transfer_direction direction,
+ unsigned long flags)
+{
+ struct dw_edma_transfer xfer;
+
+ xfer.dchan = dchan;
+ xfer.direction = direction;
+ xfer.xfer.cyclic.paddr = paddr;
+ xfer.xfer.cyclic.len = len;
+ xfer.xfer.cyclic.cnt = count;
+ xfer.flags = flags;
+ xfer.cyclic = true;
+
+ return dw_edma_device_transfer(&xfer);
+}
+
+static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
+{
+ struct dw_edma_desc *desc;
+ struct virt_dma_desc *vd;
+ unsigned long flags;
+
+ dw_edma_v0_core_clear_done_int(chan);
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ vd = vchan_next_desc(&chan->vc);
+ if (vd) {
+ switch (chan->request) {
+ case EDMA_REQ_NONE:
+ desc = vd2dw_edma_desc(vd);
+ if (desc->chunks_alloc) {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ } else {
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ chan->status = EDMA_ST_IDLE;
+ }
+ break;
+ case EDMA_REQ_STOP:
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_IDLE;
+ break;
+ case EDMA_REQ_PAUSE:
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_PAUSE;
+ break;
+ default:
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+}
+
+static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
+{
+ struct virt_dma_desc *vd;
+ unsigned long flags;
+
+ dw_edma_v0_core_clear_abort_int(chan);
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ vd = vchan_next_desc(&chan->vc);
+ if (vd) {
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_IDLE;
+}
+
+static irqreturn_t dw_edma_interrupt(int irq, void *data, bool write)
+{
+ struct dw_edma_irq *dw_irq = data;
+ struct dw_edma *dw = dw_irq->dw;
+ unsigned long total, pos, val;
+ unsigned long off;
+ u32 mask;
+
+ if (write) {
+ total = dw->wr_ch_cnt;
+ off = 0;
+ mask = dw_irq->wr_mask;
+ } else {
+ total = dw->rd_ch_cnt;
+ off = dw->wr_ch_cnt;
+ mask = dw_irq->rd_mask;
+ }
+
+ val = dw_edma_v0_core_status_done_int(dw, write ?
+ EDMA_DIR_WRITE :
+ EDMA_DIR_READ);
+ val &= mask;
+ for_each_set_bit(pos, &val, total) {
+ struct dw_edma_chan *chan = &dw->chan[pos + off];
+
+ dw_edma_done_interrupt(chan);
+ }
+
+ val = dw_edma_v0_core_status_abort_int(dw, write ?
+ EDMA_DIR_WRITE :
+ EDMA_DIR_READ);
+ val &= mask;
+ for_each_set_bit(pos, &val, total) {
+ struct dw_edma_chan *chan = &dw->chan[pos + off];
+
+ dw_edma_abort_interrupt(chan);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
+{
+ return dw_edma_interrupt(irq, data, true);
+}
+
+static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
+{
+ return dw_edma_interrupt(irq, data, false);
+}
+
+static irqreturn_t dw_edma_interrupt_common(int irq, void *data)
+{
+ dw_edma_interrupt(irq, data, true);
+ dw_edma_interrupt(irq, data, false);
+
+ return IRQ_HANDLED;
+}
+
+static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ if (chan->status != EDMA_ST_IDLE)
+ return -EBUSY;
+
+ dma_cookie_init(dchan);
+
+ pm_runtime_get(chan->chip->dev);
+
+ return 0;
+}
+
+static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(5000);
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int ret;
+
+ while (time_before(jiffies, timeout)) {
+ ret = dw_edma_device_terminate_all(dchan);
+ if (!ret)
+ break;
+
+ if (time_after_eq(jiffies, timeout))
+ return;
+
+ cpu_relax();
+ };
+
+ pm_runtime_put(chan->chip->dev);
+}
+
+static int dw_edma_channel_setup(struct dw_edma_chip *chip, bool write,
+ u32 wr_alloc, u32 rd_alloc)
+{
+ struct dw_edma_region *dt_region;
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ struct dw_edma_chan *chan;
+ size_t ll_chunk, dt_chunk;
+ struct dw_edma_irq *irq;
+ struct dma_device *dma;
+ u32 i, j, cnt, ch_cnt;
+ u32 alloc, off_alloc;
+ int err = 0;
+ u32 pos;
+
+ ch_cnt = dw->wr_ch_cnt + dw->rd_ch_cnt;
+ ll_chunk = dw->ll_region.sz;
+ dt_chunk = dw->dt_region.sz;
+
+ /* Calculate linked list chunk for each channel */
+ ll_chunk /= roundup_pow_of_two(ch_cnt);
+
+ /* Calculate linked list chunk for each channel */
+ dt_chunk /= roundup_pow_of_two(ch_cnt);
+
+ if (write) {
+ i = 0;
+ cnt = dw->wr_ch_cnt;
+ dma = &dw->wr_edma;
+ alloc = wr_alloc;
+ off_alloc = 0;
+ } else {
+ i = dw->wr_ch_cnt;
+ cnt = dw->rd_ch_cnt;
+ dma = &dw->rd_edma;
+ alloc = rd_alloc;
+ off_alloc = wr_alloc;
+ }
+
+ INIT_LIST_HEAD(&dma->channels);
+ for (j = 0; (alloc || dw->nr_irqs == 1) && j < cnt; j++, i++) {
+ chan = &dw->chan[i];
+
+ dt_region = devm_kzalloc(dev, sizeof(*dt_region), GFP_KERNEL);
+ if (!dt_region)
+ return -ENOMEM;
+
+ chan->vc.chan.private = dt_region;
+
+ chan->chip = chip;
+ chan->id = j;
+ chan->dir = write ? EDMA_DIR_WRITE : EDMA_DIR_READ;
+ chan->configured = false;
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_IDLE;
+
+ chan->ll_off = (ll_chunk * i);
+ chan->ll_max = (ll_chunk / EDMA_LL_SZ) - 1;
+
+ chan->dt_off = (dt_chunk * i);
+
+ dev_vdbg(dev, "L. List:\tChannel %s[%u] off=0x%.8lx, max_cnt=%u\n",
+ write ? "write" : "read", j,
+ chan->ll_off, chan->ll_max);
+
+ if (dw->nr_irqs == 1)
+ pos = 0;
+ else
+ pos = off_alloc + (j % alloc);
+
+ irq = &dw->irq[pos];
+
+ if (write)
+ irq->wr_mask |= BIT(j);
+ else
+ irq->rd_mask |= BIT(j);
+
+ irq->dw = dw;
+ memcpy(&chan->msi, &irq->msi, sizeof(chan->msi));
+
+ dev_vdbg(dev, "MSI:\t\tChannel %s[%u] addr=0x%.8x%.8x, data=0x%.8x\n",
+ write ? "write" : "read", j,
+ chan->msi.address_hi, chan->msi.address_lo,
+ chan->msi.data);
+
+ chan->vc.desc_free = vchan_free_desc;
+ vchan_init(&chan->vc, dma);
+
+ dt_region->paddr = dw->dt_region.paddr + chan->dt_off;
+ dt_region->vaddr = dw->dt_region.vaddr + chan->dt_off;
+ dt_region->sz = dt_chunk;
+
+ dev_vdbg(dev, "Data:\tChannel %s[%u] off=0x%.8lx\n",
+ write ? "write" : "read", j, chan->dt_off);
+
+ dw_edma_v0_core_device_config(chan);
+ }
+
+ /* Set DMA channel capabilities */
+ dma_cap_zero(dma->cap_mask);
+ dma_cap_set(DMA_SLAVE, dma->cap_mask);
+ dma_cap_set(DMA_CYCLIC, dma->cap_mask);
+ dma_cap_set(DMA_PRIVATE, dma->cap_mask);
+ dma->directions = BIT(write ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV);
+ dma->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ dma->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
+ dma->chancnt = cnt;
+
+ /* Set DMA channel callbacks */
+ dma->dev = chip->dev;
+ dma->device_alloc_chan_resources = dw_edma_alloc_chan_resources;
+ dma->device_free_chan_resources = dw_edma_free_chan_resources;
+ dma->device_config = dw_edma_device_config;
+ dma->device_pause = dw_edma_device_pause;
+ dma->device_resume = dw_edma_device_resume;
+ dma->device_terminate_all = dw_edma_device_terminate_all;
+ dma->device_issue_pending = dw_edma_device_issue_pending;
+ dma->device_tx_status = dw_edma_device_tx_status;
+ dma->device_prep_slave_sg = dw_edma_device_prep_slave_sg;
+ dma->device_prep_dma_cyclic = dw_edma_device_prep_dma_cyclic;
+
+ dma_set_max_seg_size(dma->dev, U32_MAX);
+
+ /* Register DMA device */
+ err = dma_async_device_register(dma);
+
+ return err;
+}
+
+static inline void dw_edma_dec_irq_alloc(int *nr_irqs, u32 *alloc, u16 cnt)
+{
+ if (*nr_irqs && *alloc < cnt) {
+ (*alloc)++;
+ (*nr_irqs)--;
+ }
+}
+
+static inline void dw_edma_add_irq_mask(u32 *mask, u32 alloc, u16 cnt)
+{
+ while (*mask * alloc < cnt)
+ (*mask)++;
+}
+
+static int dw_edma_irq_request(struct dw_edma_chip *chip,
+ u32 *wr_alloc, u32 *rd_alloc)
+{
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ u32 wr_mask = 1;
+ u32 rd_mask = 1;
+ int i, err = 0;
+ u32 ch_cnt;
+
+ ch_cnt = dw->wr_ch_cnt + dw->rd_ch_cnt;
+
+ if (dw->nr_irqs < 1)
+ return -EINVAL;
+
+ if (dw->nr_irqs == 1) {
+ /* Common IRQ shared among all channels */
+ err = request_irq(pci_irq_vector(to_pci_dev(dev), 0),
+ dw_edma_interrupt_common,
+ IRQF_SHARED, dw->name, &dw->irq[0]);
+ if (err) {
+ dw->nr_irqs = 0;
+ return err;
+ }
+
+ get_cached_msi_msg(pci_irq_vector(to_pci_dev(dev), 0),
+ &dw->irq[0].msi);
+ } else {
+ /* Distribute IRQs equally among all channels */
+ int tmp = dw->nr_irqs;
+
+ while (tmp && (*wr_alloc + *rd_alloc) < ch_cnt) {
+ dw_edma_dec_irq_alloc(&tmp, wr_alloc, dw->wr_ch_cnt);
+ dw_edma_dec_irq_alloc(&tmp, rd_alloc, dw->rd_ch_cnt);
+ }
+
+ dw_edma_add_irq_mask(&wr_mask, *wr_alloc, dw->wr_ch_cnt);
+ dw_edma_add_irq_mask(&rd_mask, *rd_alloc, dw->rd_ch_cnt);
+
+ for (i = 0; i < (*wr_alloc + *rd_alloc); i++) {
+ err = request_irq(pci_irq_vector(to_pci_dev(dev), i),
+ i < *wr_alloc ?
+ dw_edma_interrupt_write :
+ dw_edma_interrupt_read,
+ IRQF_SHARED, dw->name,
+ &dw->irq[i]);
+ if (err) {
+ dw->nr_irqs = i;
+ return err;
+ }
+
+ get_cached_msi_msg(pci_irq_vector(to_pci_dev(dev), i),
+ &dw->irq[i].msi);
+ }
+
+ dw->nr_irqs = i;
+ }
+
+ return err;
+}
+
+int dw_edma_probe(struct dw_edma_chip *chip)
+{
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ u32 wr_alloc = 0;
+ u32 rd_alloc = 0;
+ int i, err;
+
+ raw_spin_lock_init(&dw->lock);
+
+ /* Find out how many write channels are supported by hardware */
+ dw->wr_ch_cnt = dw_edma_v0_core_ch_count(dw, EDMA_DIR_WRITE);
+ if (!dw->wr_ch_cnt)
+ return -EINVAL;
+
+ /* Find out how many read channels are supported by hardware */
+ dw->rd_ch_cnt = dw_edma_v0_core_ch_count(dw, EDMA_DIR_READ);
+ if (!dw->rd_ch_cnt)
+ return -EINVAL;
+
+ dev_vdbg(dev, "Channels:\twrite=%d, read=%d\n",
+ dw->wr_ch_cnt, dw->rd_ch_cnt);
+
+ /* Allocate channels */
+ dw->chan = devm_kcalloc(dev, dw->wr_ch_cnt + dw->rd_ch_cnt,
+ sizeof(*dw->chan), GFP_KERNEL);
+ if (!dw->chan)
+ return -ENOMEM;
+
+ snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%d", chip->id);
+
+ /* Disable eDMA, only to establish the ideal initial conditions */
+ dw_edma_v0_core_off(dw);
+
+ /* Request IRQs */
+ err = dw_edma_irq_request(chip, &wr_alloc, &rd_alloc);
+ if (err)
+ return err;
+
+ /* Setup write channels */
+ err = dw_edma_channel_setup(chip, true, wr_alloc, rd_alloc);
+ if (err)
+ goto err_irq_free;
+
+ /* Setup read channels */
+ err = dw_edma_channel_setup(chip, false, wr_alloc, rd_alloc);
+ if (err)
+ goto err_irq_free;
+
+ /* Power management */
+ pm_runtime_enable(dev);
+
+ /* Turn debugfs on */
+ err = dw_edma_v0_core_debugfs_on(chip);
+ if (err)
+ goto err_pm_disable;
+
+ return 0;
+
+err_pm_disable:
+ pm_runtime_disable(dev);
+err_irq_free:
+ for (i = (dw->nr_irqs - 1); i >= 0; i--)
+ free_irq(pci_irq_vector(to_pci_dev(dev), i), &dw->irq[i]);
+
+ dw->nr_irqs = 0;
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(dw_edma_probe);
+
+int dw_edma_remove(struct dw_edma_chip *chip)
+{
+ struct dw_edma_chan *chan, *_chan;
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ int i;
+
+ /* Disable eDMA */
+ dw_edma_v0_core_off(dw);
+
+ /* Free irqs */
+ for (i = (dw->nr_irqs - 1); i >= 0; i--)
+ free_irq(pci_irq_vector(to_pci_dev(dev), i), &dw->irq[i]);
+
+ /* Power management */
+ pm_runtime_disable(dev);
+
+ list_for_each_entry_safe(chan, _chan, &dw->wr_edma.channels,
+ vc.chan.device_node) {
+ list_del(&chan->vc.chan.device_node);
+ tasklet_kill(&chan->vc.task);
+ }
+
+ list_for_each_entry_safe(chan, _chan, &dw->rd_edma.channels,
+ vc.chan.device_node) {
+ list_del(&chan->vc.chan.device_node);
+ tasklet_kill(&chan->vc.task);
+ }
+
+ /* Deregister eDMA device */
+ dma_async_device_unregister(&dw->wr_edma);
+ dma_async_device_unregister(&dw->rd_edma);
+
+ /* Turn debugfs off */
+ dw_edma_v0_core_debugfs_off();
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_edma_remove);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Synopsys DesignWare eDMA controller core driver");
+MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>");
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
new file mode 100644
index 0000000..8a3a0a4
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA core driver
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#ifndef _DW_EDMA_CORE_H
+#define _DW_EDMA_CORE_H
+
+#include <linux/msi.h>
+#include <linux/dma/edma.h>
+
+#include "../virt-dma.h"
+
+#define EDMA_LL_SZ 24
+
+enum dw_edma_dir {
+ EDMA_DIR_WRITE = 0,
+ EDMA_DIR_READ
+};
+
+enum dw_edma_mode {
+ EDMA_MODE_LEGACY = 0,
+ EDMA_MODE_UNROLL
+};
+
+enum dw_edma_request {
+ EDMA_REQ_NONE = 0,
+ EDMA_REQ_STOP,
+ EDMA_REQ_PAUSE
+};
+
+enum dw_edma_status {
+ EDMA_ST_IDLE = 0,
+ EDMA_ST_PAUSE,
+ EDMA_ST_BUSY
+};
+
+struct dw_edma_chan;
+struct dw_edma_chunk;
+
+struct dw_edma_burst {
+ struct list_head list;
+ u64 sar;
+ u64 dar;
+ u32 sz;
+};
+
+struct dw_edma_region {
+ phys_addr_t paddr;
+ dma_addr_t vaddr;
+ size_t sz;
+};
+
+struct dw_edma_chunk {
+ struct list_head list;
+ struct dw_edma_chan *chan;
+ struct dw_edma_burst *burst;
+
+ u32 bursts_alloc;
+
+ u8 cb;
+ struct dw_edma_region ll_region; /* Linked list */
+};
+
+struct dw_edma_desc {
+ struct virt_dma_desc vd;
+ struct dw_edma_chan *chan;
+ struct dw_edma_chunk *chunk;
+
+ u32 chunks_alloc;
+
+ u32 alloc_sz;
+ u32 xfer_sz;
+};
+
+struct dw_edma_chan {
+ struct virt_dma_chan vc;
+ struct dw_edma_chip *chip;
+ int id;
+ enum dw_edma_dir dir;
+
+ off_t ll_off;
+ u32 ll_max;
+
+ off_t dt_off;
+
+ struct msi_msg msi;
+
+ enum dw_edma_request request;
+ enum dw_edma_status status;
+ u8 configured;
+
+ struct dma_slave_config config;
+};
+
+struct dw_edma_irq {
+ struct msi_msg msi;
+ u32 wr_mask;
+ u32 rd_mask;
+ struct dw_edma *dw;
+};
+
+struct dw_edma {
+ char name[20];
+
+ struct dma_device wr_edma;
+ u16 wr_ch_cnt;
+
+ struct dma_device rd_edma;
+ u16 rd_ch_cnt;
+
+ struct dw_edma_region rg_region; /* Registers */
+ struct dw_edma_region ll_region; /* Linked list */
+ struct dw_edma_region dt_region; /* Data */
+
+ struct dw_edma_irq *irq;
+ int nr_irqs;
+
+ u32 version;
+ enum dw_edma_mode mode;
+
+ struct dw_edma_chan *chan;
+ const struct dw_edma_core_ops *ops;
+
+ raw_spinlock_t lock; /* Only for legacy */
+};
+
+struct dw_edma_sg {
+ struct scatterlist *sgl;
+ unsigned int len;
+};
+
+struct dw_edma_cyclic {
+ dma_addr_t paddr;
+ size_t len;
+ size_t cnt;
+};
+
+struct dw_edma_transfer {
+ struct dma_chan *dchan;
+ union Xfer {
+ struct dw_edma_sg sg;
+ struct dw_edma_cyclic cyclic;
+ } xfer;
+ enum dma_transfer_direction direction;
+ unsigned long flags;
+ bool cyclic;
+};
+
+static inline
+struct dw_edma_chan *vc2dw_edma_chan(struct virt_dma_chan *vc)
+{
+ return container_of(vc, struct dw_edma_chan, vc);
+}
+
+static inline
+struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan)
+{
+ return vc2dw_edma_chan(to_virt_chan(dchan));
+}
+
+#endif /* _DW_EDMA_CORE_H */
diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h
new file mode 100644
index 0000000..cab6e18
--- /dev/null
+++ b/include/linux/dma/edma.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA core driver
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#ifndef _DW_EDMA_H
+#define _DW_EDMA_H
+
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+
+struct dw_edma;
+
+/**
+ * struct dw_edma_chip - representation of DesignWare eDMA controller hardware
+ * @dev: struct device of the eDMA controller
+ * @id: instance ID
+ * @irq: irq line
+ * @dw: struct dw_edma that is filed by dw_edma_probe()
+ */
+struct dw_edma_chip {
+ struct device *dev;
+ int id;
+ int irq;
+ struct dw_edma *dw;
+};
+
+/* Export to the platform drivers */
+#if IS_ENABLED(CONFIG_DW_EDMA)
+int dw_edma_probe(struct dw_edma_chip *chip);
+int dw_edma_remove(struct dw_edma_chip *chip);
+#else
+static inline int dw_edma_probe(struct dw_edma_chip *chip)
+{
+ return -ENODEV;
+}
+
+static inline int dw_edma_remove(struct dw_edma_chip *chip)
+{
+ return 0;
+}
+#endif /* CONFIG_DW_EDMA */
+
+#endif /* _DW_EDMA_H */
^ permalink raw reply related
* [RFC,v6,2/6] dmaengine: Add Synopsys eDMA IP version 0 support
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Vinod Koul, Dan Williams, Andy Shevchenko,
Russell King, Niklas Cassel, Joao Pinto
Add support for the eDMA IP version 0 driver for both register maps (legacy
and unroll).
The legacy register mapping was the initial implementation, which consisted
in having all registers belonging to channels multiplexed, which could be
change anytime (which could led a race-condition) by view port register
(access to only one channel available each time).
This register mapping is not very effective and efficient in a multithread
environment, which has led to the development of unroll registers mapping,
which consists of having all channels registers accessible any time by
spreading all channels registers by an offset between them.
This version supports a maximum of 16 independent channels (8 write +
8 read), which can run simultaneously.
Implements a scatter-gather transfer through a linked list, where the size
of linked list depends on the allocated memory divided equally among all
channels.
Each linked list descriptor can transfer from 1 byte to 4 Gbytes and is
alignmented to DWORD.
Both SAR (Source Address Register) and DAR (Destination Address Register)
are alignmented to byte.
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Russell King <rmk+kernel@armlinux.org.uk>
Cc: Niklas Cassel <niklas.cassel@linaro.org>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- Replace comments // (C99 style) by /**/
- Replace magic numbers by defines
- Replace boolean return from ternary operation by a double negation
operation
- Replace QWORD_HI/QWORD_LO macros by upper_32_bits()/lower_32_bits()
- Fix the headers of the .c and .h files according to the most recent
convention
- Fix errors and checks pointed out by checkpatch with --strict option
- Replace patch small description tag from dma by dmaengine
- Refactor code to replace atomic_t by u32 variable type
RFC v2->RFC v3:
- Code rewrite to use FIELD_PREP() and FIELD_GET()
- Add define to magic numbers
- Fix minor bugs
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- Fix license header
- Fix MSI data mask for even and odd channels
- Code rewrite to use direct dw-edma-v0-core functions instead of
callbacks
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Add author on file header
drivers/dma/dw-edma/Makefile | 3 +-
drivers/dma/dw-edma/dw-edma-core.c | 1 +
drivers/dma/dw-edma/dw-edma-v0-core.c | 347 ++++++++++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-v0-core.h | 28 +++
drivers/dma/dw-edma/dw-edma-v0-regs.h | 158 ++++++++++++++++
5 files changed, 536 insertions(+), 1 deletion(-)
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-core.c
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-core.h
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-regs.h
diff --git a/drivers/dma/dw-edma/Makefile b/drivers/dma/dw-edma/Makefile
index 3224010..01c7c63 100644
--- a/drivers/dma/dw-edma/Makefile
+++ b/drivers/dma/dw-edma/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DW_EDMA) += dw-edma.o
-dw-edma-objs := dw-edma-core.o
+dw-edma-objs := dw-edma-core.o \
+ dw-edma-v0-core.o
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 54cd531..15fc642 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -17,6 +17,7 @@
#include <linux/pci.h>
#include "dw-edma-core.h"
+#include "dw-edma-v0-core.h"
#include "../dmaengine.h"
#include "../virt-dma.h"
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
new file mode 100644
index 0000000..aa5d3f7
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA v0 core
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#include <linux/bitfield.h>
+
+#include "dw-edma-core.h"
+#include "dw-edma-v0-core.h"
+#include "dw-edma-v0-regs.h"
+#include "dw-edma-v0-debugfs.h"
+
+enum dw_edma_control {
+ DW_EDMA_V0_CB = BIT(0),
+ DW_EDMA_V0_TCB = BIT(1),
+ DW_EDMA_V0_LLP = BIT(2),
+ DW_EDMA_V0_LIE = BIT(3),
+ DW_EDMA_V0_RIE = BIT(4),
+ DW_EDMA_V0_CCS = BIT(8),
+ DW_EDMA_V0_LLE = BIT(9),
+};
+
+static inline struct dw_edma_v0_regs __iomem *__dw_regs(struct dw_edma *dw)
+{
+ return (struct dw_edma_v0_regs __iomem *)dw->rg_region.vaddr;
+}
+
+#define SET(dw, name, value) \
+ writel(value, &(__dw_regs(dw)->name))
+
+#define GET(dw, name) \
+ readl(&(__dw_regs(dw)->name))
+
+#define SET_RW(dw, dir, name, value) \
+ do { \
+ if ((dir) == EDMA_DIR_WRITE) \
+ SET(dw, wr_##name, value); \
+ else \
+ SET(dw, rd_##name, value); \
+ } while (0)
+
+#define GET_RW(dw, dir, name) \
+ ((dir) == EDMA_DIR_WRITE \
+ ? GET(dw, wr_##name) \
+ : GET(dw, rd_##name))
+
+#define SET_BOTH(dw, name, value) \
+ do { \
+ SET(dw, wr_##name, value); \
+ SET(dw, rd_##name, value); \
+ } while (0)
+
+static inline struct dw_edma_v0_ch_regs __iomem *
+__dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch)
+{
+ if (dw->mode == EDMA_MODE_LEGACY)
+ return &(__dw_regs(dw)->type.legacy.ch);
+
+ if (dir == EDMA_DIR_WRITE)
+ return &__dw_regs(dw)->type.unroll.ch[ch].wr;
+
+ return &__dw_regs(dw)->type.unroll.ch[ch].rd;
+}
+
+static inline void writel_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch,
+ u32 value, void __iomem *addr)
+{
+ if (dw->mode == EDMA_MODE_LEGACY) {
+ u32 viewport_sel;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&dw->lock, flags);
+
+ viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch);
+ if (dir == EDMA_DIR_READ)
+ viewport_sel |= BIT(31);
+
+ writel(viewport_sel,
+ &(__dw_regs(dw)->type.legacy.viewport_sel));
+ writel(value, addr);
+
+ raw_spin_unlock_irqrestore(&dw->lock, flags);
+ } else {
+ writel(value, addr);
+ }
+}
+
+static inline u32 readl_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch,
+ const void __iomem *addr)
+{
+ u32 value;
+
+ if (dw->mode == EDMA_MODE_LEGACY) {
+ u32 viewport_sel;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&dw->lock, flags);
+
+ viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch);
+ if (dir == EDMA_DIR_READ)
+ viewport_sel |= BIT(31);
+
+ writel(viewport_sel,
+ &(__dw_regs(dw)->type.legacy.viewport_sel));
+ value = readl(addr);
+
+ raw_spin_unlock_irqrestore(&dw->lock, flags);
+ } else {
+ value = readl(addr);
+ }
+
+ return value;
+}
+
+#define SET_CH(dw, dir, ch, name, value) \
+ writel_ch(dw, dir, ch, value, &(__dw_ch_regs(dw, dir, ch)->name))
+
+#define GET_CH(dw, dir, ch, name) \
+ readl_ch(dw, dir, ch, &(__dw_ch_regs(dw, dir, ch)->name))
+
+#define SET_LL(ll, value) \
+ writel(value, ll)
+
+/* eDMA management callbacks */
+void dw_edma_v0_core_off(struct dw_edma *dw)
+{
+ SET_BOTH(dw, int_mask, EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);
+ SET_BOTH(dw, int_clear, EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);
+ SET_BOTH(dw, engine_en, 0);
+}
+
+u16 dw_edma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
+{
+ u32 num_ch;
+
+ if (dir == EDMA_DIR_WRITE)
+ num_ch = FIELD_GET(EDMA_V0_WRITE_CH_COUNT_MASK, GET(dw, ctrl));
+ else
+ num_ch = FIELD_GET(EDMA_V0_READ_CH_COUNT_MASK, GET(dw, ctrl));
+
+ if (num_ch > EDMA_V0_MAX_NR_CH)
+ num_ch = EDMA_V0_MAX_NR_CH;
+
+ return (u16)num_ch;
+}
+
+enum dma_status dw_edma_v0_core_ch_status(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->chip->dw;
+ u32 tmp;
+
+ tmp = FIELD_GET(EDMA_V0_CH_STATUS_MASK,
+ GET_CH(dw, chan->dir, chan->id, ch_control1));
+
+ if (tmp == 1)
+ return DMA_IN_PROGRESS;
+ else if (tmp == 3)
+ return DMA_COMPLETE;
+ else
+ return DMA_ERROR;
+}
+
+void dw_edma_v0_core_clear_done_int(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->chip->dw;
+
+ SET_RW(dw, chan->dir, int_clear,
+ FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)));
+}
+
+void dw_edma_v0_core_clear_abort_int(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->chip->dw;
+
+ SET_RW(dw, chan->dir, int_clear,
+ FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)));
+}
+
+u32 dw_edma_v0_core_status_done_int(struct dw_edma *dw, enum dw_edma_dir dir)
+{
+ return FIELD_GET(EDMA_V0_DONE_INT_MASK, GET_RW(dw, dir, int_status));
+}
+
+u32 dw_edma_v0_core_status_abort_int(struct dw_edma *dw, enum dw_edma_dir dir)
+{
+ return FIELD_GET(EDMA_V0_ABORT_INT_MASK, GET_RW(dw, dir, int_status));
+}
+
+static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
+{
+ struct dw_edma_burst *child;
+ struct dw_edma_v0_lli *lli;
+ struct dw_edma_v0_llp *llp;
+ u32 control = 0, i = 0;
+ u64 sar, dar, addr;
+ int j;
+
+ lli = (struct dw_edma_v0_lli *)chunk->ll_region.vaddr;
+
+ if (chunk->cb)
+ control = DW_EDMA_V0_CB;
+
+ j = chunk->bursts_alloc;
+ list_for_each_entry(child, &chunk->burst->list, list) {
+ j--;
+ if (!j)
+ control |= (DW_EDMA_V0_LIE | DW_EDMA_V0_RIE);
+
+ /* Channel control */
+ SET_LL(&lli[i].control, control);
+ /* Transfer size */
+ SET_LL(&lli[i].transfer_size, child->sz);
+ /* SAR - low, high */
+ sar = cpu_to_le64(child->sar);
+ SET_LL(&lli[i].sar_low, lower_32_bits(sar));
+ SET_LL(&lli[i].sar_high, upper_32_bits(sar));
+ /* DAR - low, high */
+ dar = cpu_to_le64(child->dar);
+ SET_LL(&lli[i].dar_low, lower_32_bits(dar));
+ SET_LL(&lli[i].dar_high, upper_32_bits(dar));
+ i++;
+ }
+
+ llp = (struct dw_edma_v0_llp *)&lli[i];
+ control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;
+ if (!chunk->cb)
+ control |= DW_EDMA_V0_CB;
+
+ /* Channel control */
+ SET_LL(&llp->control, control);
+ /* Linked list - low, high */
+ addr = cpu_to_le64(chunk->ll_region.paddr);
+ SET_LL(&llp->llp_low, lower_32_bits(addr));
+ SET_LL(&llp->llp_high, upper_32_bits(addr));
+}
+
+void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
+{
+ struct dw_edma_chan *chan = chunk->chan;
+ struct dw_edma *dw = chan->chip->dw;
+ u32 tmp;
+ u64 llp;
+
+ dw_edma_v0_core_write_chunk(chunk);
+
+ if (first) {
+ /* Enable engine */
+ SET_RW(dw, chan->dir, engine_en, BIT(0));
+ /* Interrupt unmask - done, abort */
+ tmp = GET_RW(dw, chan->dir, int_mask);
+ tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
+ tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
+ SET_RW(dw, chan->dir, int_mask, tmp);
+ /* Linked list error */
+ tmp = GET_RW(dw, chan->dir, linked_list_err_en);
+ tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
+ SET_RW(dw, chan->dir, linked_list_err_en, tmp);
+ /* Channel control */
+ SET_CH(dw, chan->dir, chan->id, ch_control1,
+ (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
+ /* Linked list - low, high */
+ llp = cpu_to_le64(chunk->ll_region.paddr);
+ SET_CH(dw, chan->dir, chan->id, llp_low, lower_32_bits(llp));
+ SET_CH(dw, chan->dir, chan->id, llp_high, upper_32_bits(llp));
+ }
+ /* Doorbell */
+ SET_RW(dw, chan->dir, doorbell,
+ FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
+}
+
+int dw_edma_v0_core_device_config(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->chip->dw;
+ u32 tmp = 0;
+
+ /* MSI done addr - low, high */
+ SET_RW(dw, chan->dir, done_imwr_low, chan->msi.address_lo);
+ SET_RW(dw, chan->dir, done_imwr_high, chan->msi.address_hi);
+ /* MSI abort addr - low, high */
+ SET_RW(dw, chan->dir, abort_imwr_low, chan->msi.address_lo);
+ SET_RW(dw, chan->dir, abort_imwr_high, chan->msi.address_hi);
+ /* MSI data - low, high */
+ switch (chan->id) {
+ case 0:
+ case 1:
+ tmp = GET_RW(dw, chan->dir, ch01_imwr_data);
+ break;
+ case 2:
+ case 3:
+ tmp = GET_RW(dw, chan->dir, ch23_imwr_data);
+ break;
+ case 4:
+ case 5:
+ tmp = GET_RW(dw, chan->dir, ch45_imwr_data);
+ break;
+ case 6:
+ case 7:
+ tmp = GET_RW(dw, chan->dir, ch67_imwr_data);
+ break;
+ }
+
+ if (chan->id & BIT(0)) {
+ /* Channel odd {1, 3, 5, 7} */
+ tmp &= EDMA_V0_CH_EVEN_MSI_DATA_MASK;
+ tmp |= FIELD_PREP(EDMA_V0_CH_ODD_MSI_DATA_MASK,
+ chan->msi.data);
+ } else {
+ /* Channel even {0, 2, 4, 6} */
+ tmp &= EDMA_V0_CH_ODD_MSI_DATA_MASK;
+ tmp |= FIELD_PREP(EDMA_V0_CH_EVEN_MSI_DATA_MASK,
+ chan->msi.data);
+ }
+
+ switch (chan->id) {
+ case 0:
+ case 1:
+ SET_RW(dw, chan->dir, ch01_imwr_data, tmp);
+ break;
+ case 2:
+ case 3:
+ SET_RW(dw, chan->dir, ch23_imwr_data, tmp);
+ break;
+ case 4:
+ case 5:
+ SET_RW(dw, chan->dir, ch45_imwr_data, tmp);
+ break;
+ case 6:
+ case 7:
+ SET_RW(dw, chan->dir, ch67_imwr_data, tmp);
+ break;
+ }
+
+ return 0;
+}
+
+/* eDMA debugfs callbacks */
+int dw_edma_v0_core_debugfs_on(struct dw_edma_chip *chip)
+{
+ return 0;
+}
+
+void dw_edma_v0_core_debugfs_off(void)
+{
+}
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.h b/drivers/dma/dw-edma/dw-edma-v0-core.h
new file mode 100644
index 0000000..f8a4d4c
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA v0 core
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#ifndef _DW_EDMA_V0_CORE_H
+#define _DW_EDMA_V0_CORE_H
+
+#include <linux/dma/edma.h>
+
+/* eDMA management callbacks */
+void dw_edma_v0_core_off(struct dw_edma *chan);
+u16 dw_edma_v0_core_ch_count(struct dw_edma *chan, enum dw_edma_dir dir);
+enum dma_status dw_edma_v0_core_ch_status(struct dw_edma_chan *chan);
+void dw_edma_v0_core_clear_done_int(struct dw_edma_chan *chan);
+void dw_edma_v0_core_clear_abort_int(struct dw_edma_chan *chan);
+u32 dw_edma_v0_core_status_done_int(struct dw_edma *chan, enum dw_edma_dir dir);
+u32 dw_edma_v0_core_status_abort_int(struct dw_edma *chan, enum dw_edma_dir dir);
+void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first);
+int dw_edma_v0_core_device_config(struct dw_edma_chan *chan);
+/* eDMA debug fs callbacks */
+int dw_edma_v0_core_debugfs_on(struct dw_edma_chip *chip);
+void dw_edma_v0_core_debugfs_off(void);
+
+#endif /* _DW_EDMA_V0_CORE_H */
diff --git a/drivers/dma/dw-edma/dw-edma-v0-regs.h b/drivers/dma/dw-edma/dw-edma-v0-regs.h
new file mode 100644
index 0000000..702f5e2
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-v0-regs.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA v0 core
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#ifndef _DW_EDMA_V0_REGS_H
+#define _DW_EDMA_V0_REGS_H
+
+#include <linux/dmaengine.h>
+
+#define EDMA_V0_MAX_NR_CH 8
+#define EDMA_V0_VIEWPORT_MASK GENMASK(2, 0)
+#define EDMA_V0_DONE_INT_MASK GENMASK(7, 0)
+#define EDMA_V0_ABORT_INT_MASK GENMASK(23, 16)
+#define EDMA_V0_WRITE_CH_COUNT_MASK GENMASK(3, 0)
+#define EDMA_V0_READ_CH_COUNT_MASK GENMASK(19, 16)
+#define EDMA_V0_CH_STATUS_MASK GENMASK(6, 5)
+#define EDMA_V0_DOORBELL_CH_MASK GENMASK(2, 0)
+#define EDMA_V0_LINKED_LIST_ERR_MASK GENMASK(7, 0)
+
+#define EDMA_V0_CH_ODD_MSI_DATA_MASK GENMASK(31, 16)
+#define EDMA_V0_CH_EVEN_MSI_DATA_MASK GENMASK(15, 0)
+
+struct dw_edma_v0_ch_regs {
+ u32 ch_control1; /* 0x000 */
+ u32 ch_control2; /* 0x004 */
+ u32 transfer_size; /* 0x008 */
+ u32 sar_low; /* 0x00c */
+ u32 sar_high; /* 0x010 */
+ u32 dar_low; /* 0x014 */
+ u32 dar_high; /* 0x018 */
+ u32 llp_low; /* 0x01c */
+ u32 llp_high; /* 0x020 */
+};
+
+struct dw_edma_v0_ch {
+ struct dw_edma_v0_ch_regs wr; /* 0x200 */
+ u32 padding_1[55]; /* [0x224..0x2fc] */
+ struct dw_edma_v0_ch_regs rd; /* 0x300 */
+ u32 padding_2[55]; /* [0x224..0x2fc] */
+};
+
+struct dw_edma_v0_unroll {
+ u32 padding_1; /* 0x0f8 */
+ u32 wr_engine_chgroup; /* 0x100 */
+ u32 rd_engine_chgroup; /* 0x104 */
+ u32 wr_engine_hshake_cnt_low; /* 0x108 */
+ u32 wr_engine_hshake_cnt_high; /* 0x10c */
+ u32 padding_2[2]; /* [0x110..0x114] */
+ u32 rd_engine_hshake_cnt_low; /* 0x118 */
+ u32 rd_engine_hshake_cnt_high; /* 0x11c */
+ u32 padding_3[2]; /* [0x120..0x124] */
+ u32 wr_ch0_pwr_en; /* 0x128 */
+ u32 wr_ch1_pwr_en; /* 0x12c */
+ u32 wr_ch2_pwr_en; /* 0x130 */
+ u32 wr_ch3_pwr_en; /* 0x134 */
+ u32 wr_ch4_pwr_en; /* 0x138 */
+ u32 wr_ch5_pwr_en; /* 0x13c */
+ u32 wr_ch6_pwr_en; /* 0x140 */
+ u32 wr_ch7_pwr_en; /* 0x144 */
+ u32 padding_4[8]; /* [0x148..0x164] */
+ u32 rd_ch0_pwr_en; /* 0x168 */
+ u32 rd_ch1_pwr_en; /* 0x16c */
+ u32 rd_ch2_pwr_en; /* 0x170 */
+ u32 rd_ch3_pwr_en; /* 0x174 */
+ u32 rd_ch4_pwr_en; /* 0x178 */
+ u32 rd_ch5_pwr_en; /* 0x18c */
+ u32 rd_ch6_pwr_en; /* 0x180 */
+ u32 rd_ch7_pwr_en; /* 0x184 */
+ u32 padding_5[30]; /* [0x188..0x1fc] */
+ struct dw_edma_v0_ch ch[EDMA_V0_MAX_NR_CH]; /* [0x200..0x1120] */
+};
+
+struct dw_edma_v0_legacy {
+ u32 viewport_sel; /* 0x0f8 */
+ struct dw_edma_v0_ch_regs ch; /* [0x100..0x120] */
+};
+
+struct dw_edma_v0_regs {
+ /* eDMA global registers */
+ u32 ctrl_data_arb_prior; /* 0x000 */
+ u32 padding_1; /* 0x004 */
+ u32 ctrl; /* 0x008 */
+ u32 wr_engine_en; /* 0x00c */
+ u32 wr_doorbell; /* 0x010 */
+ u32 padding_2; /* 0x014 */
+ u32 wr_ch_arb_weight_low; /* 0x018 */
+ u32 wr_ch_arb_weight_high; /* 0x01c */
+ u32 padding_3[3]; /* [0x020..0x028] */
+ u32 rd_engine_en; /* 0x02c */
+ u32 rd_doorbell; /* 0x030 */
+ u32 padding_4; /* 0x034 */
+ u32 rd_ch_arb_weight_low; /* 0x038 */
+ u32 rd_ch_arb_weight_high; /* 0x03c */
+ u32 padding_5[3]; /* [0x040..0x048] */
+ /* eDMA interrupts registers */
+ u32 wr_int_status; /* 0x04c */
+ u32 padding_6; /* 0x050 */
+ u32 wr_int_mask; /* 0x054 */
+ u32 wr_int_clear; /* 0x058 */
+ u32 wr_err_status; /* 0x05c */
+ u32 wr_done_imwr_low; /* 0x060 */
+ u32 wr_done_imwr_high; /* 0x064 */
+ u32 wr_abort_imwr_low; /* 0x068 */
+ u32 wr_abort_imwr_high; /* 0x06c */
+ u32 wr_ch01_imwr_data; /* 0x070 */
+ u32 wr_ch23_imwr_data; /* 0x074 */
+ u32 wr_ch45_imwr_data; /* 0x078 */
+ u32 wr_ch67_imwr_data; /* 0x07c */
+ u32 padding_7[4]; /* [0x080..0x08c] */
+ u32 wr_linked_list_err_en; /* 0x090 */
+ u32 padding_8[3]; /* [0x094..0x09c] */
+ u32 rd_int_status; /* 0x0a0 */
+ u32 padding_9; /* 0x0a4 */
+ u32 rd_int_mask; /* 0x0a8 */
+ u32 rd_int_clear; /* 0x0ac */
+ u32 padding_10; /* 0x0b0 */
+ u32 rd_err_status_low; /* 0x0b4 */
+ u32 rd_err_status_high; /* 0x0b8 */
+ u32 padding_11[2]; /* [0x0bc..0x0c0] */
+ u32 rd_linked_list_err_en; /* 0x0c4 */
+ u32 padding_12; /* 0x0c8 */
+ u32 rd_done_imwr_low; /* 0x0cc */
+ u32 rd_done_imwr_high; /* 0x0d0 */
+ u32 rd_abort_imwr_low; /* 0x0d4 */
+ u32 rd_abort_imwr_high; /* 0x0d8 */
+ u32 rd_ch01_imwr_data; /* 0x0dc */
+ u32 rd_ch23_imwr_data; /* 0x0e0 */
+ u32 rd_ch45_imwr_data; /* 0x0e4 */
+ u32 rd_ch67_imwr_data; /* 0x0e8 */
+ u32 padding_13[4]; /* [0x0ec..0x0f8] */
+ /* eDMA channel context grouping */
+ union Type {
+ struct dw_edma_v0_legacy legacy; /* [0x0f8..0x120] */
+ struct dw_edma_v0_unroll unroll; /* [0x0f8..0x1120] */
+ } type;
+};
+
+struct dw_edma_v0_lli {
+ u32 control;
+ u32 transfer_size;
+ u32 sar_low;
+ u32 sar_high;
+ u32 dar_low;
+ u32 dar_high;
+};
+
+struct dw_edma_v0_llp {
+ u32 control;
+ u32 reserved;
+ u32 llp_low;
+ u32 llp_high;
+};
+
+#endif /* _DW_EDMA_V0_REGS_H */
^ permalink raw reply related
* [RFC,v6,3/6] dmaengine: Add Synopsys eDMA IP version 0 debugfs support
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Vinod Koul, Dan Williams, Andy Shevchenko,
Russell King, Joao Pinto
Add Synopsys eDMA IP version 0 debugfs support to assist any debug
in the future.
Creates a file system structure composed by folders and files that mimic
the IP register map (this files are read only) to ease any debug.
To enable this feature is necessary to select DEBUG_FS option on kernel
configuration.
Small output example:
(eDMA IP version 0, unroll, 1 write + 1 read channels)
% mount -t debugfs none /sys/kernel/debug/
% tree /sys/kernel/debug/dw-edma-core:0/
dw-edma/
├── version
├── mode
├── wr_ch_cnt
├── rd_ch_cnt
└── registers
├── ctrl_data_arb_prior
├── ctrl
├── write
│ ├── engine_en
│ ├── doorbell
│ ├── ch_arb_weight_low
│ ├── ch_arb_weight_high
│ ├── int_status
│ ├── int_mask
│ ├── int_clear
│ ├── err_status
│ ├── done_imwr_low
│ ├── done_imwr_high
│ ├── abort_imwr_low
│ ├── abort_imwr_high
│ ├── ch01_imwr_data
│ ├── ch23_imwr_data
│ ├── ch45_imwr_data
│ ├── ch67_imwr_data
│ ├── linked_list_err_en
│ ├── engine_chgroup
│ ├── engine_hshake_cnt_low
│ ├── engine_hshake_cnt_high
│ ├── ch0_pwr_en
│ ├── ch1_pwr_en
│ ├── ch2_pwr_en
│ ├── ch3_pwr_en
│ ├── ch4_pwr_en
│ ├── ch5_pwr_en
│ ├── ch6_pwr_en
│ ├── ch7_pwr_en
│ └── channel:0
│ ├── ch_control1
│ ├── ch_control2
│ ├── transfer_size
│ ├── sar_low
│ ├── sar_high
│ ├── dar_high
│ ├── llp_low
│ └── llp_high
└── read
├── engine_en
├── doorbell
├── ch_arb_weight_low
├── ch_arb_weight_high
├── int_status
├── int_mask
├── int_clear
├── err_status_low
├── err_status_high
├── done_imwr_low
├── done_imwr_high
├── abort_imwr_low
├── abort_imwr_high
├── ch01_imwr_data
├── ch23_imwr_data
├── ch45_imwr_data
├── ch67_imwr_data
├── linked_list_err_en
├── engine_chgroup
├── engine_hshake_cnt_low
├── engine_hshake_cnt_high
├── ch0_pwr_en
├── ch1_pwr_en
├── ch2_pwr_en
├── ch3_pwr_en
├── ch4_pwr_en
├── ch5_pwr_en
├── ch6_pwr_en
├── ch7_pwr_en
└── channel:0
├── ch_control1
├── ch_control2
├── transfer_size
├── sar_low
├── sar_high
├── dar_high
├── llp_low
└── llp_high
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Russell King <rmk+kernel@armlinux.org.uk>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- Replace comments // (C99 style) by /**/
- Fix the headers of the .c and .h files according to the most recent
convention
- Fix errors and checks pointed out by checkpatch with --strict option
- Replace patch small description tag from dma by dmaengine
RFC v2->RFC v3:
- Code rewrite to use FIELD_PREP() and FIELD_GET()
- Add define to magic numbers
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- Replaced number of entries of a struct calculation by a helper macro
- Reorder variables declaration in reverse tree order on several
functions
- Fix inline function declarations when CONFIG_DEBUG_FS is not defined
- Fix license header
- Add missing cast
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Add author on file header
drivers/dma/dw-edma/Makefile | 3 +-
drivers/dma/dw-edma/dw-edma-v0-core.c | 3 +-
drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 361 +++++++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-v0-debugfs.h | 28 +++
4 files changed, 393 insertions(+), 2 deletions(-)
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-debugfs.c
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-debugfs.h
diff --git a/drivers/dma/dw-edma/Makefile b/drivers/dma/dw-edma/Makefile
index 01c7c63..0c53033 100644
--- a/drivers/dma/dw-edma/Makefile
+++ b/drivers/dma/dw-edma/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DW_EDMA) += dw-edma.o
+dw-edma-$(CONFIG_DEBUG_FS) := dw-edma-v0-debugfs.o
dw-edma-objs := dw-edma-core.o \
- dw-edma-v0-core.o
+ dw-edma-v0-core.o $(dw-edma-y)
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index aa5d3f7..2fde20c 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -339,9 +339,10 @@ int dw_edma_v0_core_device_config(struct dw_edma_chan *chan)
/* eDMA debugfs callbacks */
int dw_edma_v0_core_debugfs_on(struct dw_edma_chip *chip)
{
- return 0;
+ return dw_edma_v0_debugfs_on(chip);
}
void dw_edma_v0_core_debugfs_off(void)
{
+ dw_edma_v0_debugfs_off();
}
diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c
new file mode 100644
index 0000000..d25497f
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA v0 core
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/bitfield.h>
+
+#include "dw-edma-v0-debugfs.h"
+#include "dw-edma-v0-regs.h"
+#include "dw-edma-core.h"
+
+#define RD_PERM 0444
+
+#define REGS_ADDR(name) \
+ ((dma_addr_t *)®s->name)
+#define REGISTER(name) \
+ { #name, REGS_ADDR(name) }
+
+#define WR_REGISTER(name) \
+ { #name, REGS_ADDR(wr_##name) }
+#define RD_REGISTER(name) \
+ { #name, REGS_ADDR(rd_##name) }
+
+#define WR_REGISTER_LEGACY(name) \
+ { #name, REGS_ADDR(type.legacy.wr_##name) }
+#define RD_REGISTER_LEGACY(name) \
+ { #name, REGS_ADDR(type.legacy.rd_##name) }
+
+#define WR_REGISTER_UNROLL(name) \
+ { #name, REGS_ADDR(type.unroll.wr_##name) }
+#define RD_REGISTER_UNROLL(name) \
+ { #name, REGS_ADDR(type.unroll.rd_##name) }
+
+#define WRITE_STR "write"
+#define READ_STR "read"
+#define CHANNEL_STR "channel"
+#define REGISTERS_STR "registers"
+
+static struct dentry *base_dir;
+static struct dw_edma *dw;
+static struct dw_edma_v0_regs *regs;
+
+static struct {
+ void *start;
+ void *end;
+} lim[2][EDMA_V0_MAX_NR_CH];
+
+struct debugfs_entries {
+ char name[24];
+ dma_addr_t *reg;
+};
+
+static int dw_edma_debugfs_u32_get(void *data, u64 *val)
+{
+ if (dw->mode == EDMA_MODE_LEGACY &&
+ data >= (void *)®s->type.legacy.ch) {
+ void *ptr = (void *)®s->type.legacy.ch;
+ u32 viewport_sel = 0;
+ unsigned long flags;
+ u16 ch;
+
+ for (ch = 0; ch < dw->wr_ch_cnt; ch++)
+ if (lim[0][ch].start >= data && data < lim[0][ch].end) {
+ ptr += (data - lim[0][ch].start);
+ goto legacy_sel_wr;
+ }
+
+ for (ch = 0; ch < dw->rd_ch_cnt; ch++)
+ if (lim[1][ch].start >= data && data < lim[1][ch].end) {
+ ptr += (data - lim[1][ch].start);
+ goto legacy_sel_rd;
+ }
+
+ return 0;
+legacy_sel_rd:
+ viewport_sel = BIT(31);
+legacy_sel_wr:
+ viewport_sel |= FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch);
+
+ raw_spin_lock_irqsave(&dw->lock, flags);
+
+ writel(viewport_sel, ®s->type.legacy.viewport_sel);
+ *val = readl((u32 *)ptr);
+
+ raw_spin_unlock_irqrestore(&dw->lock, flags);
+ } else {
+ *val = readl((u32 *)data);
+ }
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_edma_debugfs_u32_get, NULL, "0x%08llx\n");
+
+static int dw_edma_debugfs_create_x32(const struct debugfs_entries entries[],
+ int nr_entries, struct dentry *dir)
+{
+ struct dentry *entry;
+ int i;
+
+ for (i = 0; i < nr_entries; i++) {
+ entry = debugfs_create_file_unsafe(entries[i].name, RD_PERM,
+ dir, entries[i].reg,
+ &fops_x32);
+ if (!entry)
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static int dw_edma_debugfs_regs_ch(struct dw_edma_v0_ch_regs *regs,
+ struct dentry *dir)
+{
+ int nr_entries;
+ const struct debugfs_entries debugfs_regs[] = {
+ REGISTER(ch_control1),
+ REGISTER(ch_control2),
+ REGISTER(transfer_size),
+ REGISTER(sar_low),
+ REGISTER(sar_high),
+ REGISTER(dar_low),
+ REGISTER(dar_high),
+ REGISTER(llp_low),
+ REGISTER(llp_high),
+ };
+
+ nr_entries = ARRAY_SIZE(debugfs_regs);
+ return dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, dir);
+}
+
+static int dw_edma_debugfs_regs_wr(struct dentry *dir)
+{
+ const struct debugfs_entries debugfs_regs[] = {
+ /* eDMA global registers */
+ WR_REGISTER(engine_en),
+ WR_REGISTER(doorbell),
+ WR_REGISTER(ch_arb_weight_low),
+ WR_REGISTER(ch_arb_weight_high),
+ /* eDMA interrupts registers */
+ WR_REGISTER(int_status),
+ WR_REGISTER(int_mask),
+ WR_REGISTER(int_clear),
+ WR_REGISTER(err_status),
+ WR_REGISTER(done_imwr_low),
+ WR_REGISTER(done_imwr_high),
+ WR_REGISTER(abort_imwr_low),
+ WR_REGISTER(abort_imwr_high),
+ WR_REGISTER(ch01_imwr_data),
+ WR_REGISTER(ch23_imwr_data),
+ WR_REGISTER(ch45_imwr_data),
+ WR_REGISTER(ch67_imwr_data),
+ WR_REGISTER(linked_list_err_en),
+ };
+ const struct debugfs_entries debugfs_unroll_regs[] = {
+ /* eDMA channel context grouping */
+ WR_REGISTER_UNROLL(engine_chgroup),
+ WR_REGISTER_UNROLL(engine_hshake_cnt_low),
+ WR_REGISTER_UNROLL(engine_hshake_cnt_high),
+ WR_REGISTER_UNROLL(ch0_pwr_en),
+ WR_REGISTER_UNROLL(ch1_pwr_en),
+ WR_REGISTER_UNROLL(ch2_pwr_en),
+ WR_REGISTER_UNROLL(ch3_pwr_en),
+ WR_REGISTER_UNROLL(ch4_pwr_en),
+ WR_REGISTER_UNROLL(ch5_pwr_en),
+ WR_REGISTER_UNROLL(ch6_pwr_en),
+ WR_REGISTER_UNROLL(ch7_pwr_en),
+ };
+ struct dentry *regs_dir, *ch_dir;
+ int nr_entries, i, err;
+ char name[16];
+
+ regs_dir = debugfs_create_dir(WRITE_STR, dir);
+ if (!regs_dir)
+ return -EPERM;
+
+ nr_entries = ARRAY_SIZE(debugfs_regs);
+ err = dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir);
+ if (err)
+ return err;
+
+ if (dw->mode == EDMA_MODE_UNROLL) {
+ nr_entries = ARRAY_SIZE(debugfs_unroll_regs);
+ err = dw_edma_debugfs_create_x32(debugfs_unroll_regs,
+ nr_entries, regs_dir);
+ if (err)
+ return err;
+ }
+
+ for (i = 0; i < dw->wr_ch_cnt; i++) {
+ snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i);
+
+ ch_dir = debugfs_create_dir(name, regs_dir);
+ if (!ch_dir)
+ return -EPERM;
+
+ err = dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].wr,
+ ch_dir);
+ if (err)
+ return err;
+
+ lim[0][i].start = ®s->type.unroll.ch[i].wr;
+ lim[0][i].end = ®s->type.unroll.ch[i].padding_1[0];
+ }
+
+ return 0;
+}
+
+static int dw_edma_debugfs_regs_rd(struct dentry *dir)
+{
+ const struct debugfs_entries debugfs_regs[] = {
+ /* eDMA global registers */
+ RD_REGISTER(engine_en),
+ RD_REGISTER(doorbell),
+ RD_REGISTER(ch_arb_weight_low),
+ RD_REGISTER(ch_arb_weight_high),
+ /* eDMA interrupts registers */
+ RD_REGISTER(int_status),
+ RD_REGISTER(int_mask),
+ RD_REGISTER(int_clear),
+ RD_REGISTER(err_status_low),
+ RD_REGISTER(err_status_high),
+ RD_REGISTER(linked_list_err_en),
+ RD_REGISTER(done_imwr_low),
+ RD_REGISTER(done_imwr_high),
+ RD_REGISTER(abort_imwr_low),
+ RD_REGISTER(abort_imwr_high),
+ RD_REGISTER(ch01_imwr_data),
+ RD_REGISTER(ch23_imwr_data),
+ RD_REGISTER(ch45_imwr_data),
+ RD_REGISTER(ch67_imwr_data),
+ };
+ const struct debugfs_entries debugfs_unroll_regs[] = {
+ /* eDMA channel context grouping */
+ RD_REGISTER_UNROLL(engine_chgroup),
+ RD_REGISTER_UNROLL(engine_hshake_cnt_low),
+ RD_REGISTER_UNROLL(engine_hshake_cnt_high),
+ RD_REGISTER_UNROLL(ch0_pwr_en),
+ RD_REGISTER_UNROLL(ch1_pwr_en),
+ RD_REGISTER_UNROLL(ch2_pwr_en),
+ RD_REGISTER_UNROLL(ch3_pwr_en),
+ RD_REGISTER_UNROLL(ch4_pwr_en),
+ RD_REGISTER_UNROLL(ch5_pwr_en),
+ RD_REGISTER_UNROLL(ch6_pwr_en),
+ RD_REGISTER_UNROLL(ch7_pwr_en),
+ };
+ struct dentry *regs_dir, *ch_dir;
+ int nr_entries, i, err;
+ char name[16];
+
+ regs_dir = debugfs_create_dir(READ_STR, dir);
+ if (!regs_dir)
+ return -EPERM;
+
+ nr_entries = ARRAY_SIZE(debugfs_regs);
+ err = dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir);
+ if (err)
+ return err;
+
+ if (dw->mode == EDMA_MODE_UNROLL) {
+ nr_entries = ARRAY_SIZE(debugfs_unroll_regs);
+ err = dw_edma_debugfs_create_x32(debugfs_unroll_regs,
+ nr_entries, regs_dir);
+ if (err)
+ return err;
+ }
+
+ for (i = 0; i < dw->rd_ch_cnt; i++) {
+ snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i);
+
+ ch_dir = debugfs_create_dir(name, regs_dir);
+ if (!ch_dir)
+ return -EPERM;
+
+ err = dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].rd,
+ ch_dir);
+ if (err)
+ return err;
+
+ lim[1][i].start = ®s->type.unroll.ch[i].rd;
+ lim[1][i].end = ®s->type.unroll.ch[i].padding_2[0];
+ }
+
+ return 0;
+}
+
+static int dw_edma_debugfs_regs(void)
+{
+ const struct debugfs_entries debugfs_regs[] = {
+ REGISTER(ctrl_data_arb_prior),
+ REGISTER(ctrl),
+ };
+ struct dentry *regs_dir;
+ int nr_entries, err;
+
+ regs_dir = debugfs_create_dir(REGISTERS_STR, base_dir);
+ if (!regs_dir)
+ return -EPERM;
+
+ nr_entries = ARRAY_SIZE(debugfs_regs);
+ err = dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir);
+ if (err)
+ return err;
+
+ err = dw_edma_debugfs_regs_wr(regs_dir);
+ if (err)
+ return err;
+
+ err = dw_edma_debugfs_regs_rd(regs_dir);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+int dw_edma_v0_debugfs_on(struct dw_edma_chip *chip)
+{
+ struct dentry *entry;
+ int err;
+
+ dw = chip->dw;
+ if (!dw)
+ return -EPERM;
+
+ regs = (struct dw_edma_v0_regs *)dw->rg_region.vaddr;
+ if (!regs)
+ return -EPERM;
+
+ base_dir = debugfs_create_dir(dw->name, 0);
+ if (!base_dir)
+ return -EPERM;
+
+ entry = debugfs_create_u32("version", RD_PERM, base_dir, &dw->version);
+ if (!entry)
+ return -EPERM;
+
+ entry = debugfs_create_u32("mode", RD_PERM, base_dir, &dw->mode);
+ if (!entry)
+ return -EPERM;
+
+ entry = debugfs_create_u16("wr_ch_cnt", RD_PERM, base_dir,
+ &dw->wr_ch_cnt);
+ if (!entry)
+ return -EPERM;
+
+ entry = debugfs_create_u16("rd_ch_cnt", RD_PERM, base_dir,
+ &dw->rd_ch_cnt);
+ if (!entry)
+ return -EPERM;
+
+ err = dw_edma_debugfs_regs();
+ return err;
+}
+
+void dw_edma_v0_debugfs_off(void)
+{
+ debugfs_remove_recursive(base_dir);
+}
diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.h b/drivers/dma/dw-edma/dw-edma-v0-debugfs.h
new file mode 100644
index 0000000..cf17820
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA v0 core
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#ifndef _DW_EDMA_V0_DEBUG_FS_H
+#define _DW_EDMA_V0_DEBUG_FS_H
+
+#include <linux/dma/edma.h>
+
+#ifdef CONFIG_DEBUG_FS
+int dw_edma_v0_debugfs_on(struct dw_edma_chip *chip);
+void dw_edma_v0_debugfs_off(void);
+#else
+static inline int dw_edma_v0_debugfs_on(struct dw_edma_chip *chip)
+{
+ return 0;
+}
+
+static inline void dw_edma_v0_debugfs_off(void)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
+
+#endif /* _DW_EDMA_V0_DEBUG_FS_H */
^ permalink raw reply related
* [RFC,v6,4/6] PCI: Add Synopsys endpoint EDDA Device ID
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Kishon Vijay Abraham I, Bjorn Helgaas,
Lorenzo Pieralisi, Joao Pinto
Create and add Synopsys Endpoint EDDA Device ID to PCI ID list, since
this ID is now being use on two different drivers (pci_endpoint_test.ko
and dw-edma-pcie.ko).
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Cc: Kishon Vijay Abraham I <kishon@ti.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- Reword subject line patch
- Reorder patch order on the series
RFC v2->RFC v3:
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- Replace id by ID on title and description
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Following patch version
drivers/misc/pci_endpoint_test.c | 2 +-
include/linux/pci_ids.h | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
index 29582fe..f65a882 100644
--- a/drivers/misc/pci_endpoint_test.c
+++ b/drivers/misc/pci_endpoint_test.c
@@ -789,7 +789,7 @@ static const struct pci_device_id pci_endpoint_test_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA74x) },
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA72x) },
{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, 0x81c0) },
- { PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, 0xedda) },
+ { PCI_DEVICE_DATA(SYNOPSYS, EDDA, NULL) },
{ }
};
MODULE_DEVICE_TABLE(pci, pci_endpoint_test_tbl);
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 70e8614..4aad69f 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -2366,6 +2366,7 @@
#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3 0xabcd
#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI 0xabce
#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31 0xabcf
+#define PCI_DEVICE_ID_SYNOPSYS_EDDA 0xedda
#define PCI_VENDOR_ID_USR 0x16ec
^ permalink raw reply related
* [RFC,v6,5/6] dmaengine: Add Synopsys eDMA IP PCIe glue-logic
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Vinod Koul, Dan Williams, Russell King,
Lorenzo Pieralisi, Joao Pinto
Synopsys eDMA IP is normally distributed along with Synopsys PCIe
EndPoint IP (depends of the use and licensing agreement).
This IP requires some basic configurations, such as:
- eDMA registers BAR
- eDMA registers offset
- eDMA registers size
- eDMA linked list memory BAR
- eDMA linked list memory offset
- eDMA linked list memory size
- eDMA data memory BAR
- eDMA data memory offset
- eDMA data memory size
- eDMA version
- eDMA mode
- IRQs available for eDMA
As a working example, PCIe glue-logic will attach to a Synopsys PCIe
EndPoint IP prototype kit (Vendor ID = 0x16c3, Device ID = 0xedda),
which has built-in an eDMA IP with this default configuration:
- eDMA registers BAR = 0
- eDMA registers offset = 0x00001000 (4 Kbytes)
- eDMA registers size = 0x00002000 (8 Kbytes)
- eDMA linked list memory BAR = 2
- eDMA linked list memory offset = 0x00000000 (0 Kbytes)
- eDMA linked list memory size = 0x00800000 (8 Mbytes)
- eDMA data memory BAR = 2
- eDMA data memory offset = 0x00800000 (8 Mbytes)
- eDMA data memory size = 0x03800000 (56 Mbytes)
- eDMA version = 0
- eDMA mode = EDMA_MODE_UNROLL
- IRQs = 1
This driver can be compile as built-in or external module in kernel.
To enable this driver just select DW_EDMA_PCIE option in kernel
configuration, however it requires and selects automatically DW_EDMA
option too.
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Russell King <rmk+kernel@armlinux.org.uk>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- Replace comments // (C99 style) by /**/
- Merge two pcim_iomap_regions() calls into just one call
- Remove pci_try_set_mwi() call
- Replace some dev_info() by dev_dbg() to reduce *noise*
- Remove pci_name(pdev) call after being call dw_edma_remove()
- Remove all power management support
- Fix the headers of the .c and .h files according to the most recent
convention
- Fix errors and checks pointed out by checkpatch with --strict option
- Replace patch small description tag from dma by dmaengine
RFC v2->RFC v3:
- Fix printk variable of phys_addr_t type
- Fix missing variable initialization (chan->configured)
- Change linked list size to 512 Kbytes
- Add data memory information
- Add register size information
- Add comments or improve existing ones
- Add possibility to work with multiple IRQs feature
- Replace MSI and MSI-X enable condition by pci_dev_msi_enabled()
- Replace code to acquire MSI(-X) address and data by
get_cached_msi_msg()
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- Replace enum dw_edma_pcie and use by enum pci_barno
- Remove unnecessary dev_info() calls
- Remove unnecessary pointer null validation
- Reorder variables declaration in reverse tree order in
dw_edma_pcie_probe()
- Remove pdata validation
- Remove disable_msix parameter
- Fix license header
- Move get_cached_msi_msg() to dw-edma-core file
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Add author on file header
drivers/dma/dw-edma/Kconfig | 9 ++
drivers/dma/dw-edma/Makefile | 1 +
drivers/dma/dw-edma/dw-edma-pcie.c | 229 +++++++++++++++++++++++++++++++++++++
3 files changed, 239 insertions(+)
create mode 100644 drivers/dma/dw-edma/dw-edma-pcie.c
diff --git a/drivers/dma/dw-edma/Kconfig b/drivers/dma/dw-edma/Kconfig
index 3016bed..c0838ce 100644
--- a/drivers/dma/dw-edma/Kconfig
+++ b/drivers/dma/dw-edma/Kconfig
@@ -7,3 +7,12 @@ config DW_EDMA
help
Support the Synopsys DesignWare eDMA controller, normally
implemented on endpoints SoCs.
+
+config DW_EDMA_PCIE
+ tristate "Synopsys DesignWare eDMA PCIe driver"
+ depends on PCI && PCI_MSI
+ select DW_EDMA
+ help
+ Provides a glue-logic between the Synopsys DesignWare
+ eDMA controller and an endpoint PCIe device. This also serves
+ as a reference design to whom desires to use this IP.
diff --git a/drivers/dma/dw-edma/Makefile b/drivers/dma/dw-edma/Makefile
index 0c53033..8d45c0d 100644
--- a/drivers/dma/dw-edma/Makefile
+++ b/drivers/dma/dw-edma/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_DW_EDMA) += dw-edma.o
dw-edma-$(CONFIG_DEBUG_FS) := dw-edma-v0-debugfs.o
dw-edma-objs := dw-edma-core.o \
dw-edma-v0-core.o $(dw-edma-y)
+obj-$(CONFIG_DW_EDMA_PCIE) += dw-edma-pcie.o
diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c
new file mode 100644
index 0000000..4c96e1c
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-pcie.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA PCIe driver
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/device.h>
+#include <linux/dma/edma.h>
+#include <linux/pci-epf.h>
+#include <linux/msi.h>
+
+#include "dw-edma-core.h"
+
+struct dw_edma_pcie_data {
+ /* eDMA registers location */
+ enum pci_barno rg_bar;
+ off_t rg_off;
+ size_t rg_sz;
+ /* eDMA memory linked list location */
+ enum pci_barno ll_bar;
+ off_t ll_off;
+ size_t ll_sz;
+ /* eDMA memory data location */
+ enum pci_barno dt_bar;
+ off_t dt_off;
+ size_t dt_sz;
+ /* Other */
+ u32 version;
+ enum dw_edma_mode mode;
+ u8 irqs;
+};
+
+static const struct dw_edma_pcie_data snps_edda_data = {
+ /* eDMA registers location */
+ .rg_bar = BAR_0,
+ .rg_off = 0x00001000, /* 4 Kbytes */
+ .rg_sz = 0x00002000, /* 8 Kbytes */
+ /* eDMA memory linked list location */
+ .ll_bar = BAR_2,
+ .ll_off = 0x00000000, /* 0 Kbytes */
+ .ll_sz = 0x00800000, /* 8 Mbytes */
+ /* eDMA memory data location */
+ .dt_bar = BAR_2,
+ .dt_off = 0x00800000, /* 8 Mbytes */
+ .dt_sz = 0x03800000, /* 56 Mbytes */
+ /* Other */
+ .version = 0,
+ .mode = EDMA_MODE_UNROLL,
+ .irqs = 1,
+};
+
+static int dw_edma_pcie_probe(struct pci_dev *pdev,
+ const struct pci_device_id *pid)
+{
+ const struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
+ struct device *dev = &pdev->dev;
+ struct dw_edma_chip *chip;
+ int err, nr_irqs;
+ struct dw_edma *dw;
+
+ /* Enable PCI device */
+ err = pcim_enable_device(pdev);
+ if (err) {
+ pci_err(pdev, "enabling device failed\n");
+ return err;
+ }
+
+ /* Mapping PCI BAR regions */
+ err = pcim_iomap_regions(pdev, BIT(pdata->rg_bar) |
+ BIT(pdata->ll_bar) |
+ BIT(pdata->dt_bar),
+ pci_name(pdev));
+ if (err) {
+ pci_err(pdev, "eDMA BAR I/O remapping failed\n");
+ return err;
+ }
+
+ pci_set_master(pdev);
+
+ /* DMA configuration */
+ err = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+ if (!err) {
+ err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+ if (err) {
+ pci_err(pdev, "consistent DMA mask 64 set failed\n");
+ return err;
+ }
+ } else {
+ pci_err(pdev, "DMA mask 64 set failed\n");
+
+ err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
+ if (err) {
+ pci_err(pdev, "DMA mask 32 set failed\n");
+ return err;
+ }
+
+ err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
+ if (err) {
+ pci_err(pdev, "consistent DMA mask 32 set failed\n");
+ return err;
+ }
+ }
+
+ /* Data structure allocation */
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ dw = devm_kzalloc(dev, sizeof(*dw), GFP_KERNEL);
+ if (!dw)
+ return -ENOMEM;
+
+ /* IRQs allocation */
+ nr_irqs = pci_alloc_irq_vectors(pdev, 1, pdata->irqs,
+ PCI_IRQ_MSI | PCI_IRQ_MSIX);
+ if (nr_irqs < 1) {
+ pci_err(pdev, "fail to alloc IRQ vector (number of IRQs=%u)\n",
+ nr_irqs);
+ return -EPERM;
+ }
+
+ /* Data structure initialization */
+ chip->dw = dw;
+ chip->dev = dev;
+ chip->id = pdev->devfn;
+ chip->irq = pdev->irq;
+
+ dw->rg_region.vaddr = (dma_addr_t)pcim_iomap_table(pdev)[pdata->rg_bar];
+ dw->rg_region.vaddr += pdata->rg_off;
+ dw->rg_region.paddr = pdev->resource[pdata->rg_bar].start;
+ dw->rg_region.paddr += pdata->rg_off;
+ dw->rg_region.sz = pdata->rg_sz;
+
+ dw->ll_region.vaddr = (dma_addr_t)pcim_iomap_table(pdev)[pdata->ll_bar];
+ dw->ll_region.vaddr += pdata->ll_off;
+ dw->ll_region.paddr = pdev->resource[pdata->ll_bar].start;
+ dw->ll_region.paddr += pdata->ll_off;
+ dw->ll_region.sz = pdata->ll_sz;
+
+ dw->dt_region.vaddr = (dma_addr_t)pcim_iomap_table(pdev)[pdata->dt_bar];
+ dw->dt_region.vaddr += pdata->dt_off;
+ dw->dt_region.paddr = pdev->resource[pdata->dt_bar].start;
+ dw->dt_region.paddr += pdata->dt_off;
+ dw->dt_region.sz = pdata->dt_sz;
+
+ dw->version = pdata->version;
+ dw->mode = pdata->mode;
+ dw->nr_irqs = nr_irqs;
+
+ /* Debug info */
+ pci_dbg(pdev, "Version:\t%u\n", dw->version);
+
+ pci_dbg(pdev, "Mode:\t%s\n",
+ dw->mode == EDMA_MODE_LEGACY ? "Legacy" : "Unroll");
+
+ pci_dbg(pdev, "Registers:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%pa, p=%pa)\n",
+ pdata->rg_bar, pdata->rg_off, pdata->rg_sz,
+ &dw->rg_region.vaddr, &dw->rg_region.paddr);
+
+ pci_dbg(pdev, "L. List:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%pa, p=%pa)\n",
+ pdata->ll_bar, pdata->ll_off, pdata->ll_sz,
+ &dw->ll_region.vaddr, &dw->ll_region.paddr);
+
+ pci_dbg(pdev, "Data:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%pa, p=%pa)\n",
+ pdata->dt_bar, pdata->dt_off, pdata->dt_sz,
+ &dw->dt_region.vaddr, &dw->dt_region.paddr);
+
+ pci_dbg(pdev, "Nr. IRQs:\t%u\n", dw->nr_irqs);
+
+ /* Validating if PCI interrupts were enabled */
+ if (!pci_dev_msi_enabled(pdev)) {
+ pci_err(pdev, "enable interrupt failed\n");
+ return -EPERM;
+ }
+
+ dw->irq = devm_kcalloc(dev, nr_irqs, sizeof(*dw->irq), GFP_KERNEL);
+ if (!dw->irq)
+ return -ENOMEM;
+
+ /* Starting eDMA driver */
+ err = dw_edma_probe(chip);
+ if (err) {
+ pci_err(pdev, "eDMA probe failed\n");
+ return err;
+ }
+
+ /* Saving data structure reference */
+ pci_set_drvdata(pdev, chip);
+
+ return 0;
+}
+
+static void dw_edma_pcie_remove(struct pci_dev *pdev)
+{
+ struct dw_edma_chip *chip = pci_get_drvdata(pdev);
+ int err;
+
+ /* Stopping eDMA driver */
+ err = dw_edma_remove(chip);
+ if (err)
+ pci_warn(pdev, "can't remove device properly: %d\n", err);
+
+ /* Freeing IRQs */
+ pci_free_irq_vectors(pdev);
+}
+
+static const struct pci_device_id dw_edma_pcie_id_table[] = {
+ { PCI_DEVICE_DATA(SYNOPSYS, EDDA, &snps_edda_data) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, dw_edma_pcie_id_table);
+
+static struct pci_driver dw_edma_pcie_driver = {
+ .name = "dw-edma-pcie",
+ .id_table = dw_edma_pcie_id_table,
+ .probe = dw_edma_pcie_probe,
+ .remove = dw_edma_pcie_remove,
+};
+
+module_pci_driver(dw_edma_pcie_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Synopsys DesignWare eDMA PCIe driver");
+MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>");
^ permalink raw reply related
* [RFC,v6,6/6] MAINTAINERS: Add Synopsys eDMA IP driver maintainer
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Vinod Koul, Eugeniy Paltsev, Joao Pinto
Add Synopsys eDMA IP driver maintainer.
This driver aims to support Synopsys eDMA IP and is normally distributed
along with Synopsys PCIe EndPoint IP (depends of the use and licensing
agreement).
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Eugeniy Paltsev <paltsev@synopsys.com>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- No changes
RFC v2->RFC v3:
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- No changes
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Following patch version
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e17ebf7..32222b8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4513,6 +4513,13 @@ L: linux-mtd@lists.infradead.org
S: Supported
F: drivers/mtd/nand/raw/denali*
+DESIGNWARE EDMA CORE IP DRIVER
+M: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+L: dmaengine@vger.kernel.org
+S: Maintained
+F: drivers/dma/dw-edma/
+F: include/linux/dma/edma.h
+
DESIGNWARE USB2 DRD IP DRIVER
M: Minas Harutyunyan <hminas@synopsys.com>
L: linux-usb@vger.kernel.org
^ permalink raw reply related
* [RFC v6 4/6] PCI: Add Synopsys endpoint EDDA Device ID
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Kishon Vijay Abraham I, Bjorn Helgaas,
Lorenzo Pieralisi, Joao Pinto
In-Reply-To: <cover.1556043127.git.gustavo.pimentel@synopsys.com>
Create and add Synopsys Endpoint EDDA Device ID to PCI ID list, since
this ID is now being use on two different drivers (pci_endpoint_test.ko
and dw-edma-pcie.ko).
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Cc: Kishon Vijay Abraham I <kishon@ti.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- Reword subject line patch
- Reorder patch order on the series
RFC v2->RFC v3:
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- Replace id by ID on title and description
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Following patch version
drivers/misc/pci_endpoint_test.c | 2 +-
include/linux/pci_ids.h | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
index 29582fe..f65a882 100644
--- a/drivers/misc/pci_endpoint_test.c
+++ b/drivers/misc/pci_endpoint_test.c
@@ -789,7 +789,7 @@ static const struct pci_device_id pci_endpoint_test_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA74x) },
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA72x) },
{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, 0x81c0) },
- { PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, 0xedda) },
+ { PCI_DEVICE_DATA(SYNOPSYS, EDDA, NULL) },
{ }
};
MODULE_DEVICE_TABLE(pci, pci_endpoint_test_tbl);
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 70e8614..4aad69f 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -2366,6 +2366,7 @@
#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3 0xabcd
#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI 0xabce
#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31 0xabcf
+#define PCI_DEVICE_ID_SYNOPSYS_EDDA 0xedda
#define PCI_VENDOR_ID_USR 0x16ec
--
2.7.4
^ permalink raw reply related
* [RFC v6 0/6] dmaengine: Add Synopsys eDMA IP driver (version 0)
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Vinod Koul, Andy Shevchenko, Russell King,
Lorenzo Pieralisi, Bjorn Helgaas, Kishon Vijay Abraham I,
Joao Pinto
Add Synopsys eDMA IP driver (version 0 and for EP side only) to Linux
kernel. This IP is generally distributed with Synopsys PCIe EndPoint IP
(depends of the use and licensing agreement), which supports:
- legacy and unroll modes
- 16 independent and concurrent channels (8 write + 8 read)
- supports linked list (scatter-gather) transfer
- each linked list descriptor can transfer from 1 byte to 4 Gbytes
- supports cyclic transfer
- PCIe EndPoint glue-logic
This patch series contains:
- eDMA core + eDMA core v0 driver (implements the interface with
DMAengine controller APIs and interfaces with eDMA HW block)
- eDMA PCIe glue-logic reference driver (attaches to Synopsys EP and
provides memory access to eDMA core driver)
Gustavo Pimentel (6):
dmaengine: Add Synopsys eDMA IP core driver
dmaengine: Add Synopsys eDMA IP version 0 support
dmaengine: Add Synopsys eDMA IP version 0 debugfs support
PCI: Add Synopsys endpoint EDDA Device ID
dmaengine: Add Synopsys eDMA IP PCIe glue-logic
MAINTAINERS: Add Synopsys eDMA IP driver maintainer
MAINTAINERS | 7 +
drivers/dma/Kconfig | 2 +
drivers/dma/Makefile | 1 +
drivers/dma/dw-edma/Kconfig | 18 +
drivers/dma/dw-edma/Makefile | 7 +
drivers/dma/dw-edma/dw-edma-core.c | 920 +++++++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-core.h | 165 ++++++
drivers/dma/dw-edma/dw-edma-pcie.c | 229 ++++++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 348 ++++++++++++
drivers/dma/dw-edma/dw-edma-v0-core.h | 28 +
drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 361 ++++++++++++
drivers/dma/dw-edma/dw-edma-v0-debugfs.h | 28 +
drivers/dma/dw-edma/dw-edma-v0-regs.h | 158 ++++++
drivers/misc/pci_endpoint_test.c | 2 +-
include/linux/dma/edma.h | 47 ++
include/linux/pci_ids.h | 1 +
16 files changed, 2321 insertions(+), 1 deletion(-)
create mode 100644 drivers/dma/dw-edma/Kconfig
create mode 100644 drivers/dma/dw-edma/Makefile
create mode 100644 drivers/dma/dw-edma/dw-edma-core.c
create mode 100644 drivers/dma/dw-edma/dw-edma-core.h
create mode 100644 drivers/dma/dw-edma/dw-edma-pcie.c
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-core.c
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-core.h
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-debugfs.c
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-debugfs.h
create mode 100644 drivers/dma/dw-edma/dw-edma-v0-regs.h
create mode 100644 include/linux/dma/edma.h
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Russell King <rmk+kernel@armlinux.org.uk>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Kishon Vijay Abraham I <kishon@ti.com>
Cc: Joao Pinto <jpinto@synopsys.com>
--
2.7.4
^ permalink raw reply
* [RFC v6 1/6] dmaengine: Add Synopsys eDMA IP core driver
From: Gustavo Pimentel @ 2019-04-23 18:30 UTC (permalink / raw)
To: linux-pci, dmaengine
Cc: Gustavo Pimentel, Vinod Koul, Dan Williams, Andy Shevchenko,
Russell King, Joao Pinto
In-Reply-To: <cover.1556043127.git.gustavo.pimentel@synopsys.com>
Add Synopsys PCIe Endpoint eDMA IP core driver to kernel.
This IP is generally distributed with Synopsys PCIe Endpoint IP (depends
of the use and licensing agreement).
This core driver, initializes and configures the eDMA IP using vma-helpers
functions and dma-engine subsystem.
This driver can be compile as built-in or external module in kernel.
To enable this driver just select DW_EDMA option in kernel configuration,
however it requires and selects automatically DMA_ENGINE and
DMA_VIRTUAL_CHANNELS option too.
In order to transfer data from point A to B as fast as possible this IP
requires a dedicated memory space containing linked list of elements.
All elements of this linked list are continuous and each one describes a
data transfer (source and destination addresses, length and a control
variable).
For the sake of simplicity, lets assume a memory space for channel write
0 which allows about 42 elements.
+---------+
| Desc #0 |-+
+---------+ |
V
+----------+
| Chunk #0 |-+
| CB = 1 | | +----------+ +-----+ +-----------+ +-----+
+----------+ +->| Burst #0 |->| ... |->| Burst #41 |->| llp |
| +----------+ +-----+ +-----------+ +-----+
V
+----------+
| Chunk #1 |-+
| CB = 0 | | +-----------+ +-----+ +-----------+ +-----+
+----------+ +->| Burst #42 |->| ... |->| Burst #83 |->| llp |
| +-----------+ +-----+ +-----------+ +-----+
V
+----------+
| Chunk #2 |-+
| CB = 1 | | +-----------+ +-----+ +------------+ +-----+
+----------+ +->| Burst #84 |->| ... |->| Burst #125 |->| llp |
| +-----------+ +-----+ +------------+ +-----+
V
+----------+
| Chunk #3 |-+
| CB = 0 | | +------------+ +-----+ +------------+ +-----+
+----------+ +->| Burst #126 |->| ... |->| Burst #129 |->| llp |
+------------+ +-----+ +------------+ +-----+
Legend:
- Linked list, also know as Chunk
- Linked list element*, also know as Burst *CB*, also know as Change Bit,
it's a control bit (and typically is toggled) that allows to easily
identify and differentiate between the current linked list and the
previous or the next one.
- LLP, is a special element that indicates the end of the linked list
element stream also informs that the next CB should be toggle
On every last Burst of the Chunk (Burst #41, Burst #83, Burst #125 or
even Burst #129) is set some flags on their control variable (RIE and
LIE bits) that will trigger the send of "done" interruption.
On the interruptions callback, is decided whether to recycle the linked
list memory space by writing a new set of Bursts elements (if still
exists Chunks to transfer) or is considered completed (if there is no
Chunks available to transfer).
On scatter-gather transfer mode, the client will submit a scatter-gather
list of n (on this case 130) elements, that will be divide in multiple
Chunks, each Chunk will have (on this case 42) a limited number of
Bursts and after transferring all Bursts, an interrupt will be
triggered, which will allow to recycle the all linked list dedicated
memory again with the new information relative to the next Chunk and
respective Burst associated and repeat the whole cycle again.
On cyclic transfer mode, the client will submit a buffer pointer, length
of it and number of repetitions, in this case each burst will correspond
directly to each repetition.
Each Burst can describes a data transfer from point A(source) to point
B(destination) with a length that can be from 1 byte up to 4 GB. Since
dedicated the memory space where the linked list will reside is limited,
the whole n burst elements will be organized in several Chunks, that
will be used later to recycle the dedicated memory space to initiate a
new sequence of data transfers.
The whole transfer is considered has completed when it was transferred
all bursts.
Currently this IP has a set well-known register map, which includes
support for legacy and unroll modes. Legacy mode is version of this
register map that has multiplexer register that allows to switch
registers between all write and read channels and the unroll modes
repeats all write and read channels registers with an offset between
them. This register map is called v0.
The IP team is creating a new register map more suitable to the latest
PCIe features, that very likely will change the map register, which this
version will be called v1. As soon as this new version is released by
the IP team the support for this version in be included on this driver.
This patch has a direct dependency of the patch ("[RFC 2/7] dmaengine:
Add Synopsys eDMA IP version 0 support").
According to the logic, patches 1, 2 and 3 should be squashed into 1
unique patch, but for the sake of simplicity of review, it was divided
in this 3 patches files.
Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Russell King <rmk+kernel@armlinux.org.uk>
Cc: Joao Pinto <jpinto@synopsys.com>
---
Changes:
RFC v1->RFC v2:
- Replace comments // (C99 style) by /**/
- Fix the headers of the .c and .h files according to the most recent
convention
- Fix errors and checks pointed out by checkpatch with --strict option
- Replace patch small description tag from dma by dmaengine
- Change some dev_info() into dev_dbg()
- Remove unnecessary zero initialization after kzalloc
- Remove direction validation on config() API, since the direction
parameter is deprecated
- Refactor code to replace atomic_t by u32 variable type
- Replace start_transfer() name by dw_edma_start_transfer()
- Add spinlock to dw_edma_device_prep_slave_sg()
- Add spinlock to dw_edma_free_chunk()
- Simplify switch case into if on dw_edma_device_pause(),
dw_edma_device_resume() and dw_edma_device_terminate_all()
RFC v2->RFC v3:
- Add driver parameter to disable msix feature
- Fix printk variable of phys_addr_t type
- Fix printk variable of __iomem type
- Fix printk variable of size_t type
- Add comments or improve existing ones
- Add possibility to work with multiple IRQs feature
- Fix source and destination addresses
- Add define to magic numbers
- Add DMA cyclic transfer feature
- Rebase to v5.0-rc1
RFC v3->RFC v4:
- Remove unnecessary dev_info() calls
- Add multiple IRQ automatic adaption feature
- Reorder variables declaration in reverse tree order on several
functions
- Add return check of dw_edma_alloc_burst() in dw_edma_alloc_chunk()
- Add return check of dw_edma_alloc_chunk() in dw_edma_alloc_desc()
- Remove pm_runtime_get_sync() call in probe()
- Fix dma_cyclic() buffer address
- Replace devm_*_irq() by *_irq() and recode accordingly
- Fix license header
- Replace kvzalloc() by kzalloc() and kvfree() by kfree()
- Move ops->device_config callback from config() to probe()
- Remove restriction to perform operation only in IDLE state on
dw_edma_device_config(), dw_edma_device_prep_slave_sg(),
dw_edma_device_prep_dma_cyclic()
- Recode and simplify slave_sg() and dma_cyclic()
- Recode and simplify interrupts and channel setup
- Recode dw_edma_device_tx_status()
- Move get_cached_msi_msg() to here
- Code rewrite to use direct dw-edma-v0-core functions instead of
callbacks
RFC v4->RFC v5:
- Patch resended, forgot to replace of '___' by '---' and to remove
duplicate signed-off
RFC v5->RFC v6:
- Add author on file header
- Remove debug prints
- Code rewrite following Andy's suggestations
drivers/dma/Kconfig | 2 +
drivers/dma/Makefile | 1 +
drivers/dma/dw-edma/Kconfig | 9 +
drivers/dma/dw-edma/Makefile | 4 +
drivers/dma/dw-edma/dw-edma-core.c | 919 +++++++++++++++++++++++++++++++++++++
drivers/dma/dw-edma/dw-edma-core.h | 165 +++++++
include/linux/dma/edma.h | 47 ++
7 files changed, 1147 insertions(+)
create mode 100644 drivers/dma/dw-edma/Kconfig
create mode 100644 drivers/dma/dw-edma/Makefile
create mode 100644 drivers/dma/dw-edma/dw-edma-core.c
create mode 100644 drivers/dma/dw-edma/dw-edma-core.h
create mode 100644 include/linux/dma/edma.h
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 0b1dfb5..58b78d2 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -665,6 +665,8 @@ source "drivers/dma/qcom/Kconfig"
source "drivers/dma/dw/Kconfig"
+source "drivers/dma/dw-edma/Kconfig"
+
source "drivers/dma/hsu/Kconfig"
source "drivers/dma/sh/Kconfig"
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 6126e1c..5bddf6f 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_DMA_SUN4I) += sun4i-dma.o
obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o
obj-$(CONFIG_DW_AXI_DMAC) += dw-axi-dmac/
obj-$(CONFIG_DW_DMAC_CORE) += dw/
+obj-$(CONFIG_DW_EDMA) += dw-edma/
obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o
obj-$(CONFIG_FSL_DMA) += fsldma.o
obj-$(CONFIG_FSL_EDMA) += fsl-edma.o fsl-edma-common.o
diff --git a/drivers/dma/dw-edma/Kconfig b/drivers/dma/dw-edma/Kconfig
new file mode 100644
index 0000000..3016bed
--- /dev/null
+++ b/drivers/dma/dw-edma/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config DW_EDMA
+ tristate "Synopsys DesignWare eDMA controller driver"
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ Support the Synopsys DesignWare eDMA controller, normally
+ implemented on endpoints SoCs.
diff --git a/drivers/dma/dw-edma/Makefile b/drivers/dma/dw-edma/Makefile
new file mode 100644
index 0000000..3224010
--- /dev/null
+++ b/drivers/dma/dw-edma/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_DW_EDMA) += dw-edma.o
+dw-edma-objs := dw-edma-core.o
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
new file mode 100644
index 0000000..54cd531
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA core driver
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/dmaengine.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/dma/edma.h>
+#include <linux/pci.h>
+
+#include "dw-edma-core.h"
+#include "../dmaengine.h"
+#include "../virt-dma.h"
+
+static inline
+struct device *dchan2dev(struct dma_chan *dchan)
+{
+ return &dchan->dev->device;
+}
+
+static inline
+struct device *chan2dev(struct dw_edma_chan *chan)
+{
+ return &chan->vc.chan.dev->device;
+}
+
+static inline
+struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd)
+{
+ return container_of(vd, struct dw_edma_desc, vd);
+}
+
+static struct dw_edma_burst *dw_edma_alloc_burst(struct dw_edma_chunk *chunk)
+{
+ struct dw_edma_burst *burst;
+
+ burst = kzalloc(sizeof(*burst), GFP_KERNEL);
+ if (unlikely(!burst))
+ return NULL;
+
+ INIT_LIST_HEAD(&burst->list);
+ if (chunk->burst) {
+ /* Create and add new element into the linked list */
+ chunk->bursts_alloc++;
+ list_add_tail(&burst->list, &chunk->burst->list);
+ } else {
+ /* List head */
+ chunk->bursts_alloc = 0;
+ chunk->burst = burst;
+ }
+
+ return burst;
+}
+
+static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc)
+{
+ struct dw_edma_chan *chan = desc->chan;
+ struct dw_edma *dw = chan->chip->dw;
+ struct dw_edma_chunk *chunk;
+
+ chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
+ if (unlikely(!chunk))
+ return NULL;
+
+ INIT_LIST_HEAD(&chunk->list);
+ chunk->chan = chan;
+ chunk->cb = !(desc->chunks_alloc % 2);
+ chunk->ll_region.paddr = dw->ll_region.paddr + chan->ll_off;
+ chunk->ll_region.vaddr = dw->ll_region.vaddr + chan->ll_off;
+
+ if (desc->chunk) {
+ /* Create and add new element into the linked list */
+ desc->chunks_alloc++;
+ list_add_tail(&chunk->list, &desc->chunk->list);
+ if (!dw_edma_alloc_burst(chunk)) {
+ kfree(chunk);
+ return NULL;
+ }
+ } else {
+ /* List head */
+ chunk->burst = NULL;
+ desc->chunks_alloc = 0;
+ desc->chunk = chunk;
+ }
+
+ return chunk;
+}
+
+static struct dw_edma_desc *dw_edma_alloc_desc(struct dw_edma_chan *chan)
+{
+ struct dw_edma_desc *desc;
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (unlikely(!desc))
+ return NULL;
+
+ desc->chan = chan;
+ if (!dw_edma_alloc_chunk(desc)) {
+ kfree(desc);
+ return NULL;
+ }
+
+ return desc;
+}
+
+static void dw_edma_free_burst(struct dw_edma_chunk *chunk)
+{
+ struct dw_edma_burst *child, *_next;
+
+ /* Remove all the list elements */
+ list_for_each_entry_safe(child, _next, &chunk->burst->list, list) {
+ list_del(&child->list);
+ kfree(child);
+ chunk->bursts_alloc--;
+ }
+
+ /* Remove the list head */
+ kfree(child);
+ chunk->burst = NULL;
+}
+
+static void dw_edma_free_chunk(struct dw_edma_desc *desc)
+{
+ struct dw_edma_chunk *child, *_next;
+
+ if (!desc->chunk)
+ return;
+
+ /* Remove all the list elements */
+ list_for_each_entry_safe(child, _next, &desc->chunk->list, list) {
+ dw_edma_free_burst(child);
+ list_del(&child->list);
+ kfree(child);
+ desc->chunks_alloc--;
+ }
+
+ /* Remove the list head */
+ kfree(child);
+ desc->chunk = NULL;
+}
+
+static void dw_edma_free_desc(struct dw_edma_desc *desc)
+{
+ dw_edma_free_chunk(desc);
+ kfree(desc);
+}
+
+static void vchan_free_desc(struct virt_dma_desc *vdesc)
+{
+ dw_edma_free_desc(vd2dw_edma_desc(vdesc));
+}
+
+static void dw_edma_start_transfer(struct dw_edma_chan *chan)
+{
+ struct dw_edma_chunk *child;
+ struct dw_edma_desc *desc;
+ struct virt_dma_desc *vd;
+
+ vd = vchan_next_desc(&chan->vc);
+ if (!vd)
+ return;
+
+ desc = vd2dw_edma_desc(vd);
+ if (!desc)
+ return;
+
+ child = list_first_entry_or_null(&desc->chunk->list,
+ struct dw_edma_chunk, list);
+ if (!child)
+ return;
+
+ dw_edma_v0_core_start(child, !desc->xfer_sz);
+ desc->xfer_sz += child->ll_region.sz;
+ dw_edma_free_burst(child);
+ list_del(&child->list);
+ kfree(child);
+ desc->chunks_alloc--;
+}
+
+static int dw_edma_device_config(struct dma_chan *dchan,
+ struct dma_slave_config *config)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ memcpy(&chan->config, config, sizeof(*config));
+ chan->configured = true;
+
+ return 0;
+}
+
+static int dw_edma_device_pause(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int err = 0;
+
+ if (!chan->configured)
+ err = -EPERM;
+ else if (chan->status != EDMA_ST_BUSY)
+ err = -EPERM;
+ else if (chan->request != EDMA_REQ_NONE)
+ err = -EPERM;
+ else
+ chan->request = EDMA_REQ_PAUSE;
+
+ return err;
+}
+
+static int dw_edma_device_resume(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int err = 0;
+
+ if (!chan->configured) {
+ err = -EPERM;
+ } else if (chan->status != EDMA_ST_PAUSE) {
+ err = -EPERM;
+ } else if (chan->request != EDMA_REQ_NONE) {
+ err = -EPERM;
+ } else {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ }
+
+ return err;
+}
+
+static int dw_edma_device_terminate_all(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int err = 0;
+ LIST_HEAD(head);
+
+ if (!chan->configured) {
+ /* Do nothing */
+ } else if (chan->status == EDMA_ST_PAUSE) {
+ chan->status = EDMA_ST_IDLE;
+ chan->configured = false;
+ } else if (chan->status == EDMA_ST_IDLE) {
+ chan->configured = false;
+ } else if (dw_edma_v0_core_ch_status(chan) == DMA_COMPLETE) {
+ /*
+ * The channel is in a false BUSY state, probably didn't
+ * receive or lost an interrupt
+ */
+ chan->status = EDMA_ST_IDLE;
+ chan->configured = false;
+ } else if (chan->request > EDMA_REQ_PAUSE) {
+ err = -EPERM;
+ } else {
+ chan->request = EDMA_REQ_STOP;
+ }
+
+ return err;
+}
+
+static void dw_edma_device_issue_pending(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ if (chan->configured && chan->request == EDMA_REQ_NONE &&
+ chan->status == EDMA_ST_IDLE && vchan_issue_pending(&chan->vc)) {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+}
+
+static enum dma_status
+dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
+ struct dma_tx_state *txstate)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ struct dw_edma_desc *desc;
+ struct virt_dma_desc *vd;
+ unsigned long flags;
+ enum dma_status ret;
+ u32 residue = 0;
+
+ ret = dma_cookie_status(dchan, cookie, txstate);
+ if (ret == DMA_COMPLETE)
+ return ret;
+
+ if (ret == DMA_IN_PROGRESS && chan->status == EDMA_ST_PAUSE)
+ ret = DMA_PAUSED;
+
+ if (!txstate)
+ goto ret_residue;
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ vd = vchan_find_desc(&chan->vc, cookie);
+ if (vd) {
+ desc = vd2dw_edma_desc(vd);
+ if (desc)
+ residue = desc->alloc_sz - desc->xfer_sz;
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+
+ret_residue:
+ dma_set_residue(txstate, residue);
+
+ return ret;
+}
+
+static struct dma_async_tx_descriptor *
+dw_edma_device_transfer(struct dw_edma_transfer *xfer)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan);
+ enum dma_transfer_direction direction = xfer->direction;
+ phys_addr_t src_addr, dst_addr;
+ struct scatterlist *sg = NULL;
+ struct dw_edma_chunk *chunk;
+ struct dw_edma_burst *burst;
+ struct dw_edma_desc *desc;
+ u32 cnt;
+ int i;
+
+ if ((direction == DMA_MEM_TO_DEV && chan->dir == EDMA_DIR_WRITE) ||
+ (direction == DMA_DEV_TO_MEM && chan->dir == EDMA_DIR_READ))
+ return NULL;
+
+ if (xfer->cyclic) {
+ if (!xfer->xfer.cyclic.len || !xfer->xfer.cyclic.cnt)
+ return NULL;
+ } else {
+ if (xfer->xfer.sg.len < 1)
+ return NULL;
+ }
+
+ if (!chan->configured)
+ return NULL;
+
+ desc = dw_edma_alloc_desc(chan);
+ if (unlikely(!desc))
+ goto err_alloc;
+
+ chunk = dw_edma_alloc_chunk(desc);
+ if (unlikely(!chunk))
+ goto err_alloc;
+
+ src_addr = chan->config.src_addr;
+ dst_addr = chan->config.dst_addr;
+
+ if (xfer->cyclic) {
+ cnt = xfer->xfer.cyclic.cnt;
+ } else {
+ cnt = xfer->xfer.sg.len;
+ sg = xfer->xfer.sg.sgl;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ if (!xfer->cyclic && !sg)
+ break;
+
+ if (chunk->bursts_alloc == chan->ll_max) {
+ chunk = dw_edma_alloc_chunk(desc);
+ if (unlikely(!chunk))
+ goto err_alloc;
+ }
+
+ burst = dw_edma_alloc_burst(chunk);
+ if (unlikely(!burst))
+ goto err_alloc;
+
+ if (xfer->cyclic)
+ burst->sz = xfer->xfer.cyclic.len;
+ else
+ burst->sz = sg_dma_len(sg);
+
+ chunk->ll_region.sz += burst->sz;
+ desc->alloc_sz += burst->sz;
+
+ if (direction == DMA_DEV_TO_MEM) {
+ burst->sar = src_addr;
+ if (xfer->cyclic) {
+ burst->dar = xfer->xfer.cyclic.paddr;
+ } else {
+ burst->dar = sg_dma_address(sg);
+ src_addr += sg_dma_len(sg);
+ }
+ } else {
+ burst->dar = dst_addr;
+ if (xfer->cyclic) {
+ burst->sar = xfer->xfer.cyclic.paddr;
+ } else {
+ burst->sar = sg_dma_address(sg);
+ dst_addr += sg_dma_len(sg);
+ }
+ }
+
+ if (!xfer->cyclic)
+ sg = sg_next(sg);
+ }
+
+ return vchan_tx_prep(&chan->vc, &desc->vd, xfer->flags);
+
+err_alloc:
+ if (desc)
+ dw_edma_free_desc(desc);
+
+ return NULL;
+}
+
+static struct dma_async_tx_descriptor *
+dw_edma_device_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
+ unsigned int len,
+ enum dma_transfer_direction direction,
+ unsigned long flags, void *context)
+{
+ struct dw_edma_transfer xfer;
+
+ xfer.dchan = dchan;
+ xfer.direction = direction;
+ xfer.xfer.sg.sgl = sgl;
+ xfer.xfer.sg.len = len;
+ xfer.flags = flags;
+ xfer.cyclic = false;
+
+ return dw_edma_device_transfer(&xfer);
+}
+
+static struct dma_async_tx_descriptor *
+dw_edma_device_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t paddr,
+ size_t len, size_t count,
+ enum dma_transfer_direction direction,
+ unsigned long flags)
+{
+ struct dw_edma_transfer xfer;
+
+ xfer.dchan = dchan;
+ xfer.direction = direction;
+ xfer.xfer.cyclic.paddr = paddr;
+ xfer.xfer.cyclic.len = len;
+ xfer.xfer.cyclic.cnt = count;
+ xfer.flags = flags;
+ xfer.cyclic = true;
+
+ return dw_edma_device_transfer(&xfer);
+}
+
+static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
+{
+ struct dw_edma_desc *desc;
+ struct virt_dma_desc *vd;
+ unsigned long flags;
+
+ dw_edma_v0_core_clear_done_int(chan);
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ vd = vchan_next_desc(&chan->vc);
+ if (vd) {
+ switch (chan->request) {
+ case EDMA_REQ_NONE:
+ desc = vd2dw_edma_desc(vd);
+ if (desc->chunks_alloc) {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ } else {
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ chan->status = EDMA_ST_IDLE;
+ }
+ break;
+ case EDMA_REQ_STOP:
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_IDLE;
+ break;
+ case EDMA_REQ_PAUSE:
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_PAUSE;
+ break;
+ default:
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+}
+
+static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
+{
+ struct virt_dma_desc *vd;
+ unsigned long flags;
+
+ dw_edma_v0_core_clear_abort_int(chan);
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ vd = vchan_next_desc(&chan->vc);
+ if (vd) {
+ list_del(&vd->node);
+ vchan_cookie_complete(vd);
+ }
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_IDLE;
+}
+
+static irqreturn_t dw_edma_interrupt(int irq, void *data, bool write)
+{
+ struct dw_edma_irq *dw_irq = data;
+ struct dw_edma *dw = dw_irq->dw;
+ unsigned long total, pos, val;
+ unsigned long off;
+ u32 mask;
+
+ if (write) {
+ total = dw->wr_ch_cnt;
+ off = 0;
+ mask = dw_irq->wr_mask;
+ } else {
+ total = dw->rd_ch_cnt;
+ off = dw->wr_ch_cnt;
+ mask = dw_irq->rd_mask;
+ }
+
+ val = dw_edma_v0_core_status_done_int(dw, write ?
+ EDMA_DIR_WRITE :
+ EDMA_DIR_READ);
+ val &= mask;
+ for_each_set_bit(pos, &val, total) {
+ struct dw_edma_chan *chan = &dw->chan[pos + off];
+
+ dw_edma_done_interrupt(chan);
+ }
+
+ val = dw_edma_v0_core_status_abort_int(dw, write ?
+ EDMA_DIR_WRITE :
+ EDMA_DIR_READ);
+ val &= mask;
+ for_each_set_bit(pos, &val, total) {
+ struct dw_edma_chan *chan = &dw->chan[pos + off];
+
+ dw_edma_abort_interrupt(chan);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
+{
+ return dw_edma_interrupt(irq, data, true);
+}
+
+static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
+{
+ return dw_edma_interrupt(irq, data, false);
+}
+
+static irqreturn_t dw_edma_interrupt_common(int irq, void *data)
+{
+ dw_edma_interrupt(irq, data, true);
+ dw_edma_interrupt(irq, data, false);
+
+ return IRQ_HANDLED;
+}
+
+static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ if (chan->status != EDMA_ST_IDLE)
+ return -EBUSY;
+
+ dma_cookie_init(dchan);
+
+ pm_runtime_get(chan->chip->dev);
+
+ return 0;
+}
+
+static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(5000);
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ int ret;
+
+ while (time_before(jiffies, timeout)) {
+ ret = dw_edma_device_terminate_all(dchan);
+ if (!ret)
+ break;
+
+ if (time_after_eq(jiffies, timeout))
+ return;
+
+ cpu_relax();
+ };
+
+ pm_runtime_put(chan->chip->dev);
+}
+
+static int dw_edma_channel_setup(struct dw_edma_chip *chip, bool write,
+ u32 wr_alloc, u32 rd_alloc)
+{
+ struct dw_edma_region *dt_region;
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ struct dw_edma_chan *chan;
+ size_t ll_chunk, dt_chunk;
+ struct dw_edma_irq *irq;
+ struct dma_device *dma;
+ u32 i, j, cnt, ch_cnt;
+ u32 alloc, off_alloc;
+ int err = 0;
+ u32 pos;
+
+ ch_cnt = dw->wr_ch_cnt + dw->rd_ch_cnt;
+ ll_chunk = dw->ll_region.sz;
+ dt_chunk = dw->dt_region.sz;
+
+ /* Calculate linked list chunk for each channel */
+ ll_chunk /= roundup_pow_of_two(ch_cnt);
+
+ /* Calculate linked list chunk for each channel */
+ dt_chunk /= roundup_pow_of_two(ch_cnt);
+
+ if (write) {
+ i = 0;
+ cnt = dw->wr_ch_cnt;
+ dma = &dw->wr_edma;
+ alloc = wr_alloc;
+ off_alloc = 0;
+ } else {
+ i = dw->wr_ch_cnt;
+ cnt = dw->rd_ch_cnt;
+ dma = &dw->rd_edma;
+ alloc = rd_alloc;
+ off_alloc = wr_alloc;
+ }
+
+ INIT_LIST_HEAD(&dma->channels);
+ for (j = 0; (alloc || dw->nr_irqs == 1) && j < cnt; j++, i++) {
+ chan = &dw->chan[i];
+
+ dt_region = devm_kzalloc(dev, sizeof(*dt_region), GFP_KERNEL);
+ if (!dt_region)
+ return -ENOMEM;
+
+ chan->vc.chan.private = dt_region;
+
+ chan->chip = chip;
+ chan->id = j;
+ chan->dir = write ? EDMA_DIR_WRITE : EDMA_DIR_READ;
+ chan->configured = false;
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_IDLE;
+
+ chan->ll_off = (ll_chunk * i);
+ chan->ll_max = (ll_chunk / EDMA_LL_SZ) - 1;
+
+ chan->dt_off = (dt_chunk * i);
+
+ dev_vdbg(dev, "L. List:\tChannel %s[%u] off=0x%.8lx, max_cnt=%u\n",
+ write ? "write" : "read", j,
+ chan->ll_off, chan->ll_max);
+
+ if (dw->nr_irqs == 1)
+ pos = 0;
+ else
+ pos = off_alloc + (j % alloc);
+
+ irq = &dw->irq[pos];
+
+ if (write)
+ irq->wr_mask |= BIT(j);
+ else
+ irq->rd_mask |= BIT(j);
+
+ irq->dw = dw;
+ memcpy(&chan->msi, &irq->msi, sizeof(chan->msi));
+
+ dev_vdbg(dev, "MSI:\t\tChannel %s[%u] addr=0x%.8x%.8x, data=0x%.8x\n",
+ write ? "write" : "read", j,
+ chan->msi.address_hi, chan->msi.address_lo,
+ chan->msi.data);
+
+ chan->vc.desc_free = vchan_free_desc;
+ vchan_init(&chan->vc, dma);
+
+ dt_region->paddr = dw->dt_region.paddr + chan->dt_off;
+ dt_region->vaddr = dw->dt_region.vaddr + chan->dt_off;
+ dt_region->sz = dt_chunk;
+
+ dev_vdbg(dev, "Data:\tChannel %s[%u] off=0x%.8lx\n",
+ write ? "write" : "read", j, chan->dt_off);
+
+ dw_edma_v0_core_device_config(chan);
+ }
+
+ /* Set DMA channel capabilities */
+ dma_cap_zero(dma->cap_mask);
+ dma_cap_set(DMA_SLAVE, dma->cap_mask);
+ dma_cap_set(DMA_CYCLIC, dma->cap_mask);
+ dma_cap_set(DMA_PRIVATE, dma->cap_mask);
+ dma->directions = BIT(write ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV);
+ dma->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ dma->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
+ dma->chancnt = cnt;
+
+ /* Set DMA channel callbacks */
+ dma->dev = chip->dev;
+ dma->device_alloc_chan_resources = dw_edma_alloc_chan_resources;
+ dma->device_free_chan_resources = dw_edma_free_chan_resources;
+ dma->device_config = dw_edma_device_config;
+ dma->device_pause = dw_edma_device_pause;
+ dma->device_resume = dw_edma_device_resume;
+ dma->device_terminate_all = dw_edma_device_terminate_all;
+ dma->device_issue_pending = dw_edma_device_issue_pending;
+ dma->device_tx_status = dw_edma_device_tx_status;
+ dma->device_prep_slave_sg = dw_edma_device_prep_slave_sg;
+ dma->device_prep_dma_cyclic = dw_edma_device_prep_dma_cyclic;
+
+ dma_set_max_seg_size(dma->dev, U32_MAX);
+
+ /* Register DMA device */
+ err = dma_async_device_register(dma);
+
+ return err;
+}
+
+static inline void dw_edma_dec_irq_alloc(int *nr_irqs, u32 *alloc, u16 cnt)
+{
+ if (*nr_irqs && *alloc < cnt) {
+ (*alloc)++;
+ (*nr_irqs)--;
+ }
+}
+
+static inline void dw_edma_add_irq_mask(u32 *mask, u32 alloc, u16 cnt)
+{
+ while (*mask * alloc < cnt)
+ (*mask)++;
+}
+
+static int dw_edma_irq_request(struct dw_edma_chip *chip,
+ u32 *wr_alloc, u32 *rd_alloc)
+{
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ u32 wr_mask = 1;
+ u32 rd_mask = 1;
+ int i, err = 0;
+ u32 ch_cnt;
+
+ ch_cnt = dw->wr_ch_cnt + dw->rd_ch_cnt;
+
+ if (dw->nr_irqs < 1)
+ return -EINVAL;
+
+ if (dw->nr_irqs == 1) {
+ /* Common IRQ shared among all channels */
+ err = request_irq(pci_irq_vector(to_pci_dev(dev), 0),
+ dw_edma_interrupt_common,
+ IRQF_SHARED, dw->name, &dw->irq[0]);
+ if (err) {
+ dw->nr_irqs = 0;
+ return err;
+ }
+
+ get_cached_msi_msg(pci_irq_vector(to_pci_dev(dev), 0),
+ &dw->irq[0].msi);
+ } else {
+ /* Distribute IRQs equally among all channels */
+ int tmp = dw->nr_irqs;
+
+ while (tmp && (*wr_alloc + *rd_alloc) < ch_cnt) {
+ dw_edma_dec_irq_alloc(&tmp, wr_alloc, dw->wr_ch_cnt);
+ dw_edma_dec_irq_alloc(&tmp, rd_alloc, dw->rd_ch_cnt);
+ }
+
+ dw_edma_add_irq_mask(&wr_mask, *wr_alloc, dw->wr_ch_cnt);
+ dw_edma_add_irq_mask(&rd_mask, *rd_alloc, dw->rd_ch_cnt);
+
+ for (i = 0; i < (*wr_alloc + *rd_alloc); i++) {
+ err = request_irq(pci_irq_vector(to_pci_dev(dev), i),
+ i < *wr_alloc ?
+ dw_edma_interrupt_write :
+ dw_edma_interrupt_read,
+ IRQF_SHARED, dw->name,
+ &dw->irq[i]);
+ if (err) {
+ dw->nr_irqs = i;
+ return err;
+ }
+
+ get_cached_msi_msg(pci_irq_vector(to_pci_dev(dev), i),
+ &dw->irq[i].msi);
+ }
+
+ dw->nr_irqs = i;
+ }
+
+ return err;
+}
+
+int dw_edma_probe(struct dw_edma_chip *chip)
+{
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ u32 wr_alloc = 0;
+ u32 rd_alloc = 0;
+ int i, err;
+
+ raw_spin_lock_init(&dw->lock);
+
+ /* Find out how many write channels are supported by hardware */
+ dw->wr_ch_cnt = dw_edma_v0_core_ch_count(dw, EDMA_DIR_WRITE);
+ if (!dw->wr_ch_cnt)
+ return -EINVAL;
+
+ /* Find out how many read channels are supported by hardware */
+ dw->rd_ch_cnt = dw_edma_v0_core_ch_count(dw, EDMA_DIR_READ);
+ if (!dw->rd_ch_cnt)
+ return -EINVAL;
+
+ dev_vdbg(dev, "Channels:\twrite=%d, read=%d\n",
+ dw->wr_ch_cnt, dw->rd_ch_cnt);
+
+ /* Allocate channels */
+ dw->chan = devm_kcalloc(dev, dw->wr_ch_cnt + dw->rd_ch_cnt,
+ sizeof(*dw->chan), GFP_KERNEL);
+ if (!dw->chan)
+ return -ENOMEM;
+
+ snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%d", chip->id);
+
+ /* Disable eDMA, only to establish the ideal initial conditions */
+ dw_edma_v0_core_off(dw);
+
+ /* Request IRQs */
+ err = dw_edma_irq_request(chip, &wr_alloc, &rd_alloc);
+ if (err)
+ return err;
+
+ /* Setup write channels */
+ err = dw_edma_channel_setup(chip, true, wr_alloc, rd_alloc);
+ if (err)
+ goto err_irq_free;
+
+ /* Setup read channels */
+ err = dw_edma_channel_setup(chip, false, wr_alloc, rd_alloc);
+ if (err)
+ goto err_irq_free;
+
+ /* Power management */
+ pm_runtime_enable(dev);
+
+ /* Turn debugfs on */
+ err = dw_edma_v0_core_debugfs_on(chip);
+ if (err)
+ goto err_pm_disable;
+
+ return 0;
+
+err_pm_disable:
+ pm_runtime_disable(dev);
+err_irq_free:
+ for (i = (dw->nr_irqs - 1); i >= 0; i--)
+ free_irq(pci_irq_vector(to_pci_dev(dev), i), &dw->irq[i]);
+
+ dw->nr_irqs = 0;
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(dw_edma_probe);
+
+int dw_edma_remove(struct dw_edma_chip *chip)
+{
+ struct dw_edma_chan *chan, *_chan;
+ struct device *dev = chip->dev;
+ struct dw_edma *dw = chip->dw;
+ int i;
+
+ /* Disable eDMA */
+ dw_edma_v0_core_off(dw);
+
+ /* Free irqs */
+ for (i = (dw->nr_irqs - 1); i >= 0; i--)
+ free_irq(pci_irq_vector(to_pci_dev(dev), i), &dw->irq[i]);
+
+ /* Power management */
+ pm_runtime_disable(dev);
+
+ list_for_each_entry_safe(chan, _chan, &dw->wr_edma.channels,
+ vc.chan.device_node) {
+ list_del(&chan->vc.chan.device_node);
+ tasklet_kill(&chan->vc.task);
+ }
+
+ list_for_each_entry_safe(chan, _chan, &dw->rd_edma.channels,
+ vc.chan.device_node) {
+ list_del(&chan->vc.chan.device_node);
+ tasklet_kill(&chan->vc.task);
+ }
+
+ /* Deregister eDMA device */
+ dma_async_device_unregister(&dw->wr_edma);
+ dma_async_device_unregister(&dw->rd_edma);
+
+ /* Turn debugfs off */
+ dw_edma_v0_core_debugfs_off();
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_edma_remove);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Synopsys DesignWare eDMA controller core driver");
+MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>");
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
new file mode 100644
index 0000000..8a3a0a4
--- /dev/null
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA core driver
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#ifndef _DW_EDMA_CORE_H
+#define _DW_EDMA_CORE_H
+
+#include <linux/msi.h>
+#include <linux/dma/edma.h>
+
+#include "../virt-dma.h"
+
+#define EDMA_LL_SZ 24
+
+enum dw_edma_dir {
+ EDMA_DIR_WRITE = 0,
+ EDMA_DIR_READ
+};
+
+enum dw_edma_mode {
+ EDMA_MODE_LEGACY = 0,
+ EDMA_MODE_UNROLL
+};
+
+enum dw_edma_request {
+ EDMA_REQ_NONE = 0,
+ EDMA_REQ_STOP,
+ EDMA_REQ_PAUSE
+};
+
+enum dw_edma_status {
+ EDMA_ST_IDLE = 0,
+ EDMA_ST_PAUSE,
+ EDMA_ST_BUSY
+};
+
+struct dw_edma_chan;
+struct dw_edma_chunk;
+
+struct dw_edma_burst {
+ struct list_head list;
+ u64 sar;
+ u64 dar;
+ u32 sz;
+};
+
+struct dw_edma_region {
+ phys_addr_t paddr;
+ dma_addr_t vaddr;
+ size_t sz;
+};
+
+struct dw_edma_chunk {
+ struct list_head list;
+ struct dw_edma_chan *chan;
+ struct dw_edma_burst *burst;
+
+ u32 bursts_alloc;
+
+ u8 cb;
+ struct dw_edma_region ll_region; /* Linked list */
+};
+
+struct dw_edma_desc {
+ struct virt_dma_desc vd;
+ struct dw_edma_chan *chan;
+ struct dw_edma_chunk *chunk;
+
+ u32 chunks_alloc;
+
+ u32 alloc_sz;
+ u32 xfer_sz;
+};
+
+struct dw_edma_chan {
+ struct virt_dma_chan vc;
+ struct dw_edma_chip *chip;
+ int id;
+ enum dw_edma_dir dir;
+
+ off_t ll_off;
+ u32 ll_max;
+
+ off_t dt_off;
+
+ struct msi_msg msi;
+
+ enum dw_edma_request request;
+ enum dw_edma_status status;
+ u8 configured;
+
+ struct dma_slave_config config;
+};
+
+struct dw_edma_irq {
+ struct msi_msg msi;
+ u32 wr_mask;
+ u32 rd_mask;
+ struct dw_edma *dw;
+};
+
+struct dw_edma {
+ char name[20];
+
+ struct dma_device wr_edma;
+ u16 wr_ch_cnt;
+
+ struct dma_device rd_edma;
+ u16 rd_ch_cnt;
+
+ struct dw_edma_region rg_region; /* Registers */
+ struct dw_edma_region ll_region; /* Linked list */
+ struct dw_edma_region dt_region; /* Data */
+
+ struct dw_edma_irq *irq;
+ int nr_irqs;
+
+ u32 version;
+ enum dw_edma_mode mode;
+
+ struct dw_edma_chan *chan;
+ const struct dw_edma_core_ops *ops;
+
+ raw_spinlock_t lock; /* Only for legacy */
+};
+
+struct dw_edma_sg {
+ struct scatterlist *sgl;
+ unsigned int len;
+};
+
+struct dw_edma_cyclic {
+ dma_addr_t paddr;
+ size_t len;
+ size_t cnt;
+};
+
+struct dw_edma_transfer {
+ struct dma_chan *dchan;
+ union Xfer {
+ struct dw_edma_sg sg;
+ struct dw_edma_cyclic cyclic;
+ } xfer;
+ enum dma_transfer_direction direction;
+ unsigned long flags;
+ bool cyclic;
+};
+
+static inline
+struct dw_edma_chan *vc2dw_edma_chan(struct virt_dma_chan *vc)
+{
+ return container_of(vc, struct dw_edma_chan, vc);
+}
+
+static inline
+struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan)
+{
+ return vc2dw_edma_chan(to_virt_chan(dchan));
+}
+
+#endif /* _DW_EDMA_CORE_H */
diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h
new file mode 100644
index 0000000..cab6e18
--- /dev/null
+++ b/include/linux/dma/edma.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare eDMA core driver
+ *
+ * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
+ */
+
+#ifndef _DW_EDMA_H
+#define _DW_EDMA_H
+
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+
+struct dw_edma;
+
+/**
+ * struct dw_edma_chip - representation of DesignWare eDMA controller hardware
+ * @dev: struct device of the eDMA controller
+ * @id: instance ID
+ * @irq: irq line
+ * @dw: struct dw_edma that is filed by dw_edma_probe()
+ */
+struct dw_edma_chip {
+ struct device *dev;
+ int id;
+ int irq;
+ struct dw_edma *dw;
+};
+
+/* Export to the platform drivers */
+#if IS_ENABLED(CONFIG_DW_EDMA)
+int dw_edma_probe(struct dw_edma_chip *chip);
+int dw_edma_remove(struct dw_edma_chip *chip);
+#else
+static inline int dw_edma_probe(struct dw_edma_chip *chip)
+{
+ return -ENODEV;
+}
+
+static inline int dw_edma_remove(struct dw_edma_chip *chip)
+{
+ return 0;
+}
+#endif /* CONFIG_DW_EDMA */
+
+#endif /* _DW_EDMA_H */
--
2.7.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox