ARM: Merge for-2635/s5p-dma
authorBen Dooks <ben-linux@fluff.org>
Wed, 19 May 2010 09:05:15 +0000 (18:05 +0900)
committerBen Dooks <ben-linux@fluff.org>
Wed, 19 May 2010 09:05:15 +0000 (18:05 +0900)
Merge branch 'for-2635/s5p-dma' into for-linus/samsung2

Conflicts:
arch/arm/mach-s5pv210/Makefile

20 files changed:
arch/arm/mach-s5p6440/Kconfig
arch/arm/mach-s5p6440/Makefile
arch/arm/mach-s5p6440/dma.c [new file with mode: 0644]
arch/arm/mach-s5p6440/include/mach/dma.h [new file with mode: 0644]
arch/arm/mach-s5p6440/include/mach/map.h
arch/arm/mach-s5p6442/Kconfig
arch/arm/mach-s5p6442/Makefile
arch/arm/mach-s5p6442/dma.c [new file with mode: 0644]
arch/arm/mach-s5p6442/include/mach/dma.h [new file with mode: 0644]
arch/arm/mach-s5p6442/include/mach/map.h
arch/arm/mach-s5pv210/Kconfig
arch/arm/mach-s5pv210/Makefile
arch/arm/mach-s5pv210/dma.c [new file with mode: 0644]
arch/arm/mach-s5pv210/include/mach/dma.h [new file with mode: 0644]
arch/arm/mach-s5pv210/include/mach/map.h
arch/arm/plat-samsung/Kconfig
arch/arm/plat-samsung/Makefile
arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h [new file with mode: 0644]
arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h [new file with mode: 0644]
arch/arm/plat-samsung/s3c-pl330.c [new file with mode: 0644]

index 4c29ff8..77aeffd 100644 (file)
@@ -9,6 +9,7 @@ if ARCH_S5P6440
 
 config CPU_S5P6440
        bool
+       select S3C_PL330_DMA
        help
          Enable S5P6440 CPU support
 
index 1ad894b..3f243dd 100644 (file)
@@ -12,7 +12,7 @@ obj-                          :=
 
 # Core support for S5P6440 system
 
-obj-$(CONFIG_CPU_S5P6440)      += cpu.o init.o clock.o gpio.o
+obj-$(CONFIG_CPU_S5P6440)      += cpu.o init.o clock.o gpio.o dma.o
 
 # machine support
 
diff --git a/arch/arm/mach-s5p6440/dma.c b/arch/arm/mach-s5p6440/dma.c
new file mode 100644 (file)
index 0000000..07606ad
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+
+#include <plat/devs.h>
+#include <plat/irqs.h>
+
+#include <mach/map.h>
+#include <mach/irqs.h>
+
+#include <plat/s3c-pl330-pdata.h>
+
+static u64 dma_dmamask = DMA_BIT_MASK(32);
+
+static struct resource s5p6440_pdma_resource[] = {
+       [0] = {
+               .start  = S5P6440_PA_PDMA,
+               .end    = S5P6440_PA_PDMA + SZ_4K,
+               .flags = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = IRQ_DMA0,
+               .end    = IRQ_DMA0,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct s3c_pl330_platdata s5p6440_pdma_pdata = {
+       .peri = {
+               [0] = DMACH_UART0_RX,
+               [1] = DMACH_UART0_TX,
+               [2] = DMACH_UART1_RX,
+               [3] = DMACH_UART1_TX,
+               [4] = DMACH_UART2_RX,
+               [5] = DMACH_UART2_TX,
+               [6] = DMACH_UART3_RX,
+               [7] = DMACH_UART3_TX,
+               [8] = DMACH_MAX,
+               [9] = DMACH_MAX,
+               [10] = DMACH_PCM0_TX,
+               [11] = DMACH_PCM0_RX,
+               [12] = DMACH_I2S0_TX,
+               [13] = DMACH_I2S0_RX,
+               [14] = DMACH_SPI0_TX,
+               [15] = DMACH_SPI0_RX,
+               [16] = DMACH_MAX,
+               [17] = DMACH_MAX,
+               [18] = DMACH_MAX,
+               [19] = DMACH_MAX,
+               [20] = DMACH_SPI1_TX,
+               [21] = DMACH_SPI1_RX,
+               [22] = DMACH_MAX,
+               [23] = DMACH_MAX,
+               [24] = DMACH_MAX,
+               [25] = DMACH_MAX,
+               [26] = DMACH_MAX,
+               [27] = DMACH_MAX,
+               [28] = DMACH_MAX,
+               [29] = DMACH_PWM,
+               [30] = DMACH_MAX,
+               [31] = DMACH_MAX,
+       },
+};
+
+static struct platform_device s5p6440_device_pdma = {
+       .name           = "s3c-pl330",
+       .id             = 1,
+       .num_resources  = ARRAY_SIZE(s5p6440_pdma_resource),
+       .resource       = s5p6440_pdma_resource,
+       .dev            = {
+               .dma_mask = &dma_dmamask,
+               .coherent_dma_mask = DMA_BIT_MASK(32),
+               .platform_data = &s5p6440_pdma_pdata,
+       },
+};
+
+static struct platform_device *s5p6440_dmacs[] __initdata = {
+       &s5p6440_device_pdma,
+};
+
+static int __init s5p6440_dma_init(void)
+{
+       platform_add_devices(s5p6440_dmacs, ARRAY_SIZE(s5p6440_dmacs));
+
+       return 0;
+}
+arch_initcall(s5p6440_dma_init);
diff --git a/arch/arm/mach-s5p6440/include/mach/dma.h b/arch/arm/mach-s5p6440/include/mach/dma.h
new file mode 100644 (file)
index 0000000..81209eb
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __MACH_DMA_H
+#define __MACH_DMA_H
+
+/* This platform uses the common S3C DMA API driver for PL330 */
+#include <plat/s3c-dma-pl330.h>
+
+#endif /* __MACH_DMA_H */
index 8924e5a..0275784 100644 (file)
@@ -29,6 +29,8 @@
 #define S5P6440_PA_VIC0                (0xE4000000)
 #define S5P_PA_VIC0            S5P6440_PA_VIC0
 
+#define S5P6440_PA_PDMA                0xE9000000
+
 #define S5P6440_PA_VIC1                (0xE4100000)
 #define S5P_PA_VIC1            S5P6440_PA_VIC1
 
index 4f3f6de..0fd41b4 100644 (file)
@@ -12,6 +12,7 @@ if ARCH_S5P6442
 config CPU_S5P6442
        bool
        select PLAT_S5P
+       select S3C_PL330_DMA
        help
          Enable S5P6442 CPU support
 
index dde39a6..17bd22e 100644 (file)
@@ -12,7 +12,7 @@ obj-                          :=
 
 # Core support for S5P6442 system
 
-obj-$(CONFIG_CPU_S5P6442)      += cpu.o init.o clock.o
+obj-$(CONFIG_CPU_S5P6442)      += cpu.o init.o clock.o dma.o
 
 # machine support
 
diff --git a/arch/arm/mach-s5p6442/dma.c b/arch/arm/mach-s5p6442/dma.c
new file mode 100644 (file)
index 0000000..ad4f870
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+
+#include <plat/devs.h>
+#include <plat/irqs.h>
+
+#include <mach/map.h>
+#include <mach/irqs.h>
+
+#include <plat/s3c-pl330-pdata.h>
+
+static u64 dma_dmamask = DMA_BIT_MASK(32);
+
+static struct resource s5p6442_pdma_resource[] = {
+       [0] = {
+               .start  = S5P6442_PA_PDMA,
+               .end    = S5P6442_PA_PDMA + SZ_4K,
+               .flags = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = IRQ_PDMA,
+               .end    = IRQ_PDMA,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct s3c_pl330_platdata s5p6442_pdma_pdata = {
+       .peri = {
+               [0] = DMACH_UART0_RX,
+               [1] = DMACH_UART0_TX,
+               [2] = DMACH_UART1_RX,
+               [3] = DMACH_UART1_TX,
+               [4] = DMACH_UART2_RX,
+               [5] = DMACH_UART2_TX,
+               [6] = DMACH_MAX,
+               [7] = DMACH_MAX,
+               [8] = DMACH_MAX,
+               [9] = DMACH_I2S0_RX,
+               [10] = DMACH_I2S0_TX,
+               [11] = DMACH_I2S0S_TX,
+               [12] = DMACH_I2S1_RX,
+               [13] = DMACH_I2S1_TX,
+               [14] = DMACH_MAX,
+               [15] = DMACH_MAX,
+               [16] = DMACH_SPI0_RX,
+               [17] = DMACH_SPI0_TX,
+               [18] = DMACH_MAX,
+               [19] = DMACH_MAX,
+               [20] = DMACH_PCM0_RX,
+               [21] = DMACH_PCM0_TX,
+               [22] = DMACH_PCM1_RX,
+               [23] = DMACH_PCM1_TX,
+               [24] = DMACH_MAX,
+               [25] = DMACH_MAX,
+               [26] = DMACH_MAX,
+               [27] = DMACH_MSM_REQ0,
+               [28] = DMACH_MSM_REQ1,
+               [29] = DMACH_MSM_REQ2,
+               [30] = DMACH_MSM_REQ3,
+               [31] = DMACH_MAX,
+       },
+};
+
+static struct platform_device s5p6442_device_pdma = {
+       .name           = "s3c-pl330",
+       .id             = 1,
+       .num_resources  = ARRAY_SIZE(s5p6442_pdma_resource),
+       .resource       = s5p6442_pdma_resource,
+       .dev            = {
+               .dma_mask = &dma_dmamask,
+               .coherent_dma_mask = DMA_BIT_MASK(32),
+               .platform_data = &s5p6442_pdma_pdata,
+       },
+};
+
+static struct platform_device *s5p6442_dmacs[] __initdata = {
+       &s5p6442_device_pdma,
+};
+
+static int __init s5p6442_dma_init(void)
+{
+       platform_add_devices(s5p6442_dmacs, ARRAY_SIZE(s5p6442_dmacs));
+
+       return 0;
+}
+arch_initcall(s5p6442_dma_init);
diff --git a/arch/arm/mach-s5p6442/include/mach/dma.h b/arch/arm/mach-s5p6442/include/mach/dma.h
new file mode 100644 (file)
index 0000000..81209eb
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __MACH_DMA_H
+#define __MACH_DMA_H
+
+/* This platform uses the common S3C DMA API driver for PL330 */
+#include <plat/s3c-dma-pl330.h>
+
+#endif /* __MACH_DMA_H */
index 685277d..a263d77 100644 (file)
@@ -34,6 +34,9 @@
 #define S5P6442_PA_VIC2                (0xE4200000)
 #define S5P_PA_VIC2            S5P6442_PA_VIC2
 
+#define S5P6442_PA_MDMA                0xE8000000
+#define S5P6442_PA_PDMA                0xE9000000
+
 #define S5P6442_PA_TIMER       (0xEA000000)
 #define S5P_PA_TIMER           S5P6442_PA_TIMER
 
index af33a1a..7601c28 100644 (file)
@@ -12,6 +12,7 @@ if ARCH_S5PV210
 config CPU_S5PV210
        bool
        select PLAT_S5P
+       select S3C_PL330_DMA
        help
          Enable S5PV210 CPU support
 
index 0acbdb3..192deac 100644 (file)
@@ -12,7 +12,7 @@ obj-                          :=
 
 # Core support for S5PV210 system
 
-obj-$(CONFIG_CPU_S5PV210)      += cpu.o init.o clock.o gpiolib.o
+obj-$(CONFIG_CPU_S5PV210)      += cpu.o init.o clock.o dma.o gpiolib.o
 
 # machine support
 
diff --git a/arch/arm/mach-s5pv210/dma.c b/arch/arm/mach-s5pv210/dma.c
new file mode 100644 (file)
index 0000000..778ad5f
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+
+#include <plat/devs.h>
+#include <plat/irqs.h>
+
+#include <mach/map.h>
+#include <mach/irqs.h>
+
+#include <plat/s3c-pl330-pdata.h>
+
+static u64 dma_dmamask = DMA_BIT_MASK(32);
+
+static struct resource s5pv210_pdma0_resource[] = {
+       [0] = {
+               .start  = S5PV210_PA_PDMA0,
+               .end    = S5PV210_PA_PDMA0 + SZ_4K,
+               .flags = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = IRQ_PDMA0,
+               .end    = IRQ_PDMA0,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct s3c_pl330_platdata s5pv210_pdma0_pdata = {
+       .peri = {
+               [0] = DMACH_UART0_RX,
+               [1] = DMACH_UART0_TX,
+               [2] = DMACH_UART1_RX,
+               [3] = DMACH_UART1_TX,
+               [4] = DMACH_UART2_RX,
+               [5] = DMACH_UART2_TX,
+               [6] = DMACH_UART3_RX,
+               [7] = DMACH_UART3_TX,
+               [8] = DMACH_MAX,
+               [9] = DMACH_I2S0_RX,
+               [10] = DMACH_I2S0_TX,
+               [11] = DMACH_I2S0S_TX,
+               [12] = DMACH_I2S1_RX,
+               [13] = DMACH_I2S1_TX,
+               [14] = DMACH_MAX,
+               [15] = DMACH_MAX,
+               [16] = DMACH_SPI0_RX,
+               [17] = DMACH_SPI0_TX,
+               [18] = DMACH_SPI1_RX,
+               [19] = DMACH_SPI1_TX,
+               [20] = DMACH_MAX,
+               [21] = DMACH_MAX,
+               [22] = DMACH_AC97_MICIN,
+               [23] = DMACH_AC97_PCMIN,
+               [24] = DMACH_AC97_PCMOUT,
+               [25] = DMACH_MAX,
+               [26] = DMACH_PWM,
+               [27] = DMACH_SPDIF,
+               [28] = DMACH_MAX,
+               [29] = DMACH_MAX,
+               [30] = DMACH_MAX,
+               [31] = DMACH_MAX,
+       },
+};
+
+static struct platform_device s5pv210_device_pdma0 = {
+       .name           = "s3c-pl330",
+       .id             = 1,
+       .num_resources  = ARRAY_SIZE(s5pv210_pdma0_resource),
+       .resource       = s5pv210_pdma0_resource,
+       .dev            = {
+               .dma_mask = &dma_dmamask,
+               .coherent_dma_mask = DMA_BIT_MASK(32),
+               .platform_data = &s5pv210_pdma0_pdata,
+       },
+};
+
+static struct resource s5pv210_pdma1_resource[] = {
+       [0] = {
+               .start  = S5PV210_PA_PDMA1,
+               .end    = S5PV210_PA_PDMA1 + SZ_4K,
+               .flags = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = IRQ_PDMA1,
+               .end    = IRQ_PDMA1,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct s3c_pl330_platdata s5pv210_pdma1_pdata = {
+       .peri = {
+               [0] = DMACH_UART0_RX,
+               [1] = DMACH_UART0_TX,
+               [2] = DMACH_UART1_RX,
+               [3] = DMACH_UART1_TX,
+               [4] = DMACH_UART2_RX,
+               [5] = DMACH_UART2_TX,
+               [6] = DMACH_UART3_RX,
+               [7] = DMACH_UART3_TX,
+               [8] = DMACH_MAX,
+               [9] = DMACH_I2S0_RX,
+               [10] = DMACH_I2S0_TX,
+               [11] = DMACH_I2S0S_TX,
+               [12] = DMACH_I2S1_RX,
+               [13] = DMACH_I2S1_TX,
+               [14] = DMACH_I2S2_RX,
+               [15] = DMACH_I2S2_TX,
+               [16] = DMACH_SPI0_RX,
+               [17] = DMACH_SPI0_TX,
+               [18] = DMACH_SPI1_RX,
+               [19] = DMACH_SPI1_TX,
+               [20] = DMACH_MAX,
+               [21] = DMACH_MAX,
+               [22] = DMACH_PCM0_RX,
+               [23] = DMACH_PCM0_TX,
+               [24] = DMACH_PCM1_RX,
+               [25] = DMACH_PCM1_TX,
+               [26] = DMACH_MSM_REQ0,
+               [27] = DMACH_MSM_REQ1,
+               [28] = DMACH_MSM_REQ2,
+               [29] = DMACH_MSM_REQ3,
+               [30] = DMACH_PCM2_RX,
+               [31] = DMACH_PCM2_TX,
+       },
+};
+
+static struct platform_device s5pv210_device_pdma1 = {
+       .name           = "s3c-pl330",
+       .id             = 2,
+       .num_resources  = ARRAY_SIZE(s5pv210_pdma1_resource),
+       .resource       = s5pv210_pdma1_resource,
+       .dev            = {
+               .dma_mask = &dma_dmamask,
+               .coherent_dma_mask = DMA_BIT_MASK(32),
+               .platform_data = &s5pv210_pdma1_pdata,
+       },
+};
+
+static struct platform_device *s5pv210_dmacs[] __initdata = {
+       &s5pv210_device_pdma0,
+       &s5pv210_device_pdma1,
+};
+
+static int __init s5pv210_dma_init(void)
+{
+       platform_add_devices(s5pv210_dmacs, ARRAY_SIZE(s5pv210_dmacs));
+
+       return 0;
+}
+arch_initcall(s5pv210_dma_init);
diff --git a/arch/arm/mach-s5pv210/include/mach/dma.h b/arch/arm/mach-s5pv210/include/mach/dma.h
new file mode 100644 (file)
index 0000000..81209eb
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __MACH_DMA_H
+#define __MACH_DMA_H
+
+/* This platform uses the common S3C DMA API driver for PL330 */
+#include <plat/s3c-dma-pl330.h>
+
+#endif /* __MACH_DMA_H */
index c22694c..c038585 100644 (file)
 
 #define S5PV210_PA_SROMC       (0xE8000000)
 
+#define S5PV210_PA_MDMA                0xFA200000
+#define S5PV210_PA_PDMA0       0xE0900000
+#define S5PV210_PA_PDMA1       0xE0A00000
+
 #define S5PV210_PA_VIC0                (0xF2000000)
 #define S5P_PA_VIC0            S5PV210_PA_VIC0
 
index 7a36cf8..5349d21 100644 (file)
@@ -203,6 +203,12 @@ config S3C_DMA
        help
          Internal configuration for S3C DMA core
 
+config S3C_PL330_DMA
+       bool
+       select PL330
+       help
+         S3C DMA API Driver for PL330 DMAC.
+
 comment "Power management"
 
 config SAMSUNG_PM_DEBUG
index 0ad820a..ca60367 100644 (file)
@@ -46,6 +46,8 @@ obj-$(CONFIG_S3C_DEV_NAND)    += dev-nand.o
 
 obj-$(CONFIG_S3C_DMA)          += dma.o
 
+obj-$(CONFIG_S3C_PL330_DMA)    += s3c-pl330.o
+
 # PM support
 
 obj-$(CONFIG_PM)               += pm.o
diff --git a/arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h b/arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h
new file mode 100644 (file)
index 0000000..5fe6721
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef        __S3C_DMA_PL330_H_
+#define        __S3C_DMA_PL330_H_
+
+#define S3C2410_DMAF_AUTOSTART         (1 << 0)
+#define S3C2410_DMAF_CIRCULAR          (1 << 1)
+
+/*
+ * PL330 can assign any channel to communicate with
+ * any of the peripherals attched to the DMAC.
+ * For the sake of consistency across client drivers,
+ * We keep the channel names unchanged and only add
+ * missing peripherals are added.
+ * Order is not important since S3C PL330 API driver
+ * use these just as IDs.
+ */
+enum dma_ch {
+       DMACH_UART0_RX,
+       DMACH_UART0_TX,
+       DMACH_UART1_RX,
+       DMACH_UART1_TX,
+       DMACH_UART2_RX,
+       DMACH_UART2_TX,
+       DMACH_UART3_RX,
+       DMACH_UART3_TX,
+       DMACH_IRDA,
+       DMACH_I2S0_RX,
+       DMACH_I2S0_TX,
+       DMACH_I2S0S_TX,
+       DMACH_I2S1_RX,
+       DMACH_I2S1_TX,
+       DMACH_I2S2_RX,
+       DMACH_I2S2_TX,
+       DMACH_SPI0_RX,
+       DMACH_SPI0_TX,
+       DMACH_SPI1_RX,
+       DMACH_SPI1_TX,
+       DMACH_SPI2_RX,
+       DMACH_SPI2_TX,
+       DMACH_AC97_MICIN,
+       DMACH_AC97_PCMIN,
+       DMACH_AC97_PCMOUT,
+       DMACH_EXTERNAL,
+       DMACH_PWM,
+       DMACH_SPDIF,
+       DMACH_HSI_RX,
+       DMACH_HSI_TX,
+       DMACH_PCM0_TX,
+       DMACH_PCM0_RX,
+       DMACH_PCM1_TX,
+       DMACH_PCM1_RX,
+       DMACH_PCM2_TX,
+       DMACH_PCM2_RX,
+       DMACH_MSM_REQ3,
+       DMACH_MSM_REQ2,
+       DMACH_MSM_REQ1,
+       DMACH_MSM_REQ0,
+       /* END Marker, also used to denote a reserved channel */
+       DMACH_MAX,
+};
+
+static inline bool s3c_dma_has_circular(void)
+{
+       return true;
+}
+
+#include <plat/dma.h>
+
+#endif /* __S3C_DMA_PL330_H_ */
diff --git a/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h b/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h
new file mode 100644 (file)
index 0000000..bf5e2a9
--- /dev/null
@@ -0,0 +1,32 @@
+/* linux/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h
+ *
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __S3C_PL330_PDATA_H
+#define __S3C_PL330_PDATA_H
+
+#include <plat/s3c-dma-pl330.h>
+
+/*
+ * Every PL330 DMAC has max 32 peripheral interfaces,
+ * of which some may be not be really used in your
+ * DMAC's configuration.
+ * Populate this array of 32 peri i/fs with relevant
+ * channel IDs for used peri i/f and DMACH_MAX for
+ * those unused.
+ *
+ * The platforms just need to provide this info
+ * to the S3C DMA API driver for PL330.
+ */
+struct s3c_pl330_platdata {
+       enum dma_ch peri[32];
+};
+
+#endif /* __S3C_PL330_PDATA_H */
diff --git a/arch/arm/plat-samsung/s3c-pl330.c b/arch/arm/plat-samsung/s3c-pl330.c
new file mode 100644 (file)
index 0000000..a91305a
--- /dev/null
@@ -0,0 +1,1224 @@
+/* linux/arch/arm/plat-samsung/s3c-pl330.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co. Ltd.
+ *     Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+#include <asm/hardware/pl330.h>
+
+#include <plat/s3c-pl330-pdata.h>
+
+/**
+ * struct s3c_pl330_dmac - Logical representation of a PL330 DMAC.
+ * @busy_chan: Number of channels currently busy.
+ * @peri: List of IDs of peripherals this DMAC can work with.
+ * @node: To attach to the global list of DMACs.
+ * @pi: PL330 configuration info for the DMAC.
+ * @kmcache: Pool to quickly allocate xfers for all channels in the dmac.
+ */
+struct s3c_pl330_dmac {
+       unsigned                busy_chan;
+       enum dma_ch             *peri;
+       struct list_head        node;
+       struct pl330_info       *pi;
+       struct kmem_cache       *kmcache;
+};
+
+/**
+ * struct s3c_pl330_xfer - A request submitted by S3C DMA clients.
+ * @token: Xfer ID provided by the client.
+ * @node: To attach to the list of xfers on a channel.
+ * @px: Xfer for PL330 core.
+ * @chan: Owner channel of this xfer.
+ */
+struct s3c_pl330_xfer {
+       void                    *token;
+       struct list_head        node;
+       struct pl330_xfer       px;
+       struct s3c_pl330_chan   *chan;
+};
+
+/**
+ * struct s3c_pl330_chan - Logical channel to communicate with
+ *     a Physical peripheral.
+ * @pl330_chan_id: Token of a hardware channel thread of PL330 DMAC.
+ *     NULL if the channel is available to be acquired.
+ * @id: ID of the peripheral that this channel can communicate with.
+ * @options: Options specified by the client.
+ * @sdaddr: Address provided via s3c2410_dma_devconfig.
+ * @node: To attach to the global list of channels.
+ * @lrq: Pointer to the last submitted pl330_req to PL330 core.
+ * @xfer_list: To manage list of xfers enqueued.
+ * @req: Two requests to communicate with the PL330 engine.
+ * @callback_fn: Callback function to the client.
+ * @rqcfg: Channel configuration for the xfers.
+ * @xfer_head: Pointer to the xfer to be next excecuted.
+ * @dmac: Pointer to the DMAC that manages this channel, NULL if the
+ *     channel is available to be acquired.
+ * @client: Client of this channel. NULL if the
+ *     channel is available to be acquired.
+ */
+struct s3c_pl330_chan {
+       void                            *pl330_chan_id;
+       enum dma_ch                     id;
+       unsigned int                    options;
+       unsigned long                   sdaddr;
+       struct list_head                node;
+       struct pl330_req                *lrq;
+       struct list_head                xfer_list;
+       struct pl330_req                req[2];
+       s3c2410_dma_cbfn_t              callback_fn;
+       struct pl330_reqcfg             rqcfg;
+       struct s3c_pl330_xfer           *xfer_head;
+       struct s3c_pl330_dmac           *dmac;
+       struct s3c2410_dma_client       *client;
+};
+
+/* All DMACs in the platform */
+static LIST_HEAD(dmac_list);
+
+/* All channels to peripherals in the platform */
+static LIST_HEAD(chan_list);
+
+/*
+ * Since we add resources(DMACs and Channels) to the global pool,
+ * we need to guard access to the resources using a global lock
+ */
+static DEFINE_SPINLOCK(res_lock);
+
+/* Returns the channel with ID 'id' in the chan_list */
+static struct s3c_pl330_chan *id_to_chan(const enum dma_ch id)
+{
+       struct s3c_pl330_chan *ch;
+
+       list_for_each_entry(ch, &chan_list, node)
+               if (ch->id == id)
+                       return ch;
+
+       return NULL;
+}
+
+/* Allocate a new channel with ID 'id' and add to chan_list */
+static void chan_add(const enum dma_ch id)
+{
+       struct s3c_pl330_chan *ch = id_to_chan(id);
+
+       /* Return if the channel already exists */
+       if (ch)
+               return;
+
+       ch = kmalloc(sizeof(*ch), GFP_KERNEL);
+       /* Return silently to work with other channels */
+       if (!ch)
+               return;
+
+       ch->id = id;
+       ch->dmac = NULL;
+
+       list_add_tail(&ch->node, &chan_list);
+}
+
+/* If the channel is not yet acquired by any client */
+static bool chan_free(struct s3c_pl330_chan *ch)
+{
+       if (!ch)
+               return false;
+
+       /* Channel points to some DMAC only when it's acquired */
+       return ch->dmac ? false : true;
+}
+
+/*
+ * Returns 0 is peripheral i/f is invalid or not present on the dmac.
+ * Index + 1, otherwise.
+ */
+static unsigned iface_of_dmac(struct s3c_pl330_dmac *dmac, enum dma_ch ch_id)
+{
+       enum dma_ch *id = dmac->peri;
+       int i;
+
+       /* Discount invalid markers */
+       if (ch_id == DMACH_MAX)
+               return 0;
+
+       for (i = 0; i < PL330_MAX_PERI; i++)
+               if (id[i] == ch_id)
+                       return i + 1;
+
+       return 0;
+}
+
+/* If all channel threads of the DMAC are busy */
+static inline bool dmac_busy(struct s3c_pl330_dmac *dmac)
+{
+       struct pl330_info *pi = dmac->pi;
+
+       return (dmac->busy_chan < pi->pcfg.num_chan) ? false : true;
+}
+
+/*
+ * Returns the number of free channels that
+ * can be handled by this dmac only.
+ */
+static unsigned ch_onlyby_dmac(struct s3c_pl330_dmac *dmac)
+{
+       enum dma_ch *id = dmac->peri;
+       struct s3c_pl330_dmac *d;
+       struct s3c_pl330_chan *ch;
+       unsigned found, count = 0;
+       enum dma_ch p;
+       int i;
+
+       for (i = 0; i < PL330_MAX_PERI; i++) {
+               p = id[i];
+               ch = id_to_chan(p);
+
+               if (p == DMACH_MAX || !chan_free(ch))
+                       continue;
+
+               found = 0;
+               list_for_each_entry(d, &dmac_list, node) {
+                       if (d != dmac && iface_of_dmac(d, ch->id)) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (!found)
+                       count++;
+       }
+
+       return count;
+}
+
+/*
+ * Measure of suitability of 'dmac' handling 'ch'
+ *
+ * 0 indicates 'dmac' can not handle 'ch' either
+ * because it is not supported by the hardware or
+ * because all dmac channels are currently busy.
+ *
+ * >0 vlaue indicates 'dmac' has the capability.
+ * The bigger the value the more suitable the dmac.
+ */
+#define MAX_SUIT       UINT_MAX
+#define MIN_SUIT       0
+
+static unsigned suitablility(struct s3c_pl330_dmac *dmac,
+               struct s3c_pl330_chan *ch)
+{
+       struct pl330_info *pi = dmac->pi;
+       enum dma_ch *id = dmac->peri;
+       struct s3c_pl330_dmac *d;
+       unsigned s;
+       int i;
+
+       s = MIN_SUIT;
+       /* If all the DMAC channel threads are busy */
+       if (dmac_busy(dmac))
+               return s;
+
+       for (i = 0; i < PL330_MAX_PERI; i++)
+               if (id[i] == ch->id)
+                       break;
+
+       /* If the 'dmac' can't talk to 'ch' */
+       if (i == PL330_MAX_PERI)
+               return s;
+
+       s = MAX_SUIT;
+       list_for_each_entry(d, &dmac_list, node) {
+               /*
+                * If some other dmac can talk to this
+                * peri and has some channel free.
+                */
+               if (d != dmac && iface_of_dmac(d, ch->id) && !dmac_busy(d)) {
+                       s = 0;
+                       break;
+               }
+       }
+       if (s)
+               return s;
+
+       s = 100;
+
+       /* Good if free chans are more, bad otherwise */
+       s += (pi->pcfg.num_chan - dmac->busy_chan) - ch_onlyby_dmac(dmac);
+
+       return s;
+}
+
+/* More than one DMAC may have capability to transfer data with the
+ * peripheral. This function assigns most suitable DMAC to manage the
+ * channel and hence communicate with the peripheral.
+ */
+static struct s3c_pl330_dmac *map_chan_to_dmac(struct s3c_pl330_chan *ch)
+{
+       struct s3c_pl330_dmac *d, *dmac = NULL;
+       unsigned sn, sl = MIN_SUIT;
+
+       list_for_each_entry(d, &dmac_list, node) {
+               sn = suitablility(d, ch);
+
+               if (sn == MAX_SUIT)
+                       return d;
+
+               if (sn > sl)
+                       dmac = d;
+       }
+
+       return dmac;
+}
+
+/* Acquire the channel for peripheral 'id' */
+static struct s3c_pl330_chan *chan_acquire(const enum dma_ch id)
+{
+       struct s3c_pl330_chan *ch = id_to_chan(id);
+       struct s3c_pl330_dmac *dmac;
+
+       /* If the channel doesn't exist or is already acquired */
+       if (!ch || !chan_free(ch)) {
+               ch = NULL;
+               goto acq_exit;
+       }
+
+       dmac = map_chan_to_dmac(ch);
+       /* If couldn't map */
+       if (!dmac) {
+               ch = NULL;
+               goto acq_exit;
+       }
+
+       dmac->busy_chan++;
+       ch->dmac = dmac;
+
+acq_exit:
+       return ch;
+}
+
+/* Delete xfer from the queue */
+static inline void del_from_queue(struct s3c_pl330_xfer *xfer)
+{
+       struct s3c_pl330_xfer *t;
+       struct s3c_pl330_chan *ch;
+       int found;
+
+       if (!xfer)
+               return;
+
+       ch = xfer->chan;
+
+       /* Make sure xfer is in the queue */
+       found = 0;
+       list_for_each_entry(t, &ch->xfer_list, node)
+               if (t == xfer) {
+                       found = 1;
+                       break;
+               }
+
+       if (!found)
+               return;
+
+       /* If xfer is last entry in the queue */
+       if (xfer->node.next == &ch->xfer_list)
+               t = list_entry(ch->xfer_list.next,
+                               struct s3c_pl330_xfer, node);
+       else
+               t = list_entry(xfer->node.next,
+                               struct s3c_pl330_xfer, node);
+
+       /* If there was only one node left */
+       if (t == xfer)
+               ch->xfer_head = NULL;
+       else if (ch->xfer_head == xfer)
+               ch->xfer_head = t;
+
+       list_del(&xfer->node);
+}
+
+/* Provides pointer to the next xfer in the queue.
+ * If CIRCULAR option is set, the list is left intact,
+ * otherwise the xfer is removed from the list.
+ * Forced delete 'pluck' can be set to override the CIRCULAR option.
+ */
+static struct s3c_pl330_xfer *get_from_queue(struct s3c_pl330_chan *ch,
+               int pluck)
+{
+       struct s3c_pl330_xfer *xfer = ch->xfer_head;
+
+       if (!xfer)
+               return NULL;
+
+       /* If xfer is last entry in the queue */
+       if (xfer->node.next == &ch->xfer_list)
+               ch->xfer_head = list_entry(ch->xfer_list.next,
+                                       struct s3c_pl330_xfer, node);
+       else
+               ch->xfer_head = list_entry(xfer->node.next,
+                                       struct s3c_pl330_xfer, node);
+
+       if (pluck || !(ch->options & S3C2410_DMAF_CIRCULAR))
+               del_from_queue(xfer);
+
+       return xfer;
+}
+
+static inline void add_to_queue(struct s3c_pl330_chan *ch,
+               struct s3c_pl330_xfer *xfer, int front)
+{
+       struct pl330_xfer *xt;
+
+       /* If queue empty */
+       if (ch->xfer_head == NULL)
+               ch->xfer_head = xfer;
+
+       xt = &ch->xfer_head->px;
+       /* If the head already submitted (CIRCULAR head) */
+       if (ch->options & S3C2410_DMAF_CIRCULAR &&
+               (xt == ch->req[0].x || xt == ch->req[1].x))
+               ch->xfer_head = xfer;
+
+       /* If this is a resubmission, it should go at the head */
+       if (front) {
+               ch->xfer_head = xfer;
+               list_add(&xfer->node, &ch->xfer_list);
+       } else {
+               list_add_tail(&xfer->node, &ch->xfer_list);
+       }
+}
+
+static inline void _finish_off(struct s3c_pl330_xfer *xfer,
+               enum s3c2410_dma_buffresult res, int ffree)
+{
+       struct s3c_pl330_chan *ch;
+
+       if (!xfer)
+               return;
+
+       ch = xfer->chan;
+
+       /* Do callback */
+       if (ch->callback_fn)
+               ch->callback_fn(NULL, xfer->token, xfer->px.bytes, res);
+
+       /* Force Free or if buffer is not needed anymore */
+       if (ffree || !(ch->options & S3C2410_DMAF_CIRCULAR))
+               kmem_cache_free(ch->dmac->kmcache, xfer);
+}
+
+static inline int s3c_pl330_submit(struct s3c_pl330_chan *ch,
+               struct pl330_req *r)
+{
+       struct s3c_pl330_xfer *xfer;
+       int ret = 0;
+
+       /* If already submitted */
+       if (r->x)
+               return 0;
+
+       xfer = get_from_queue(ch, 0);
+       if (xfer) {
+               r->x = &xfer->px;
+
+               /* Use max bandwidth for M<->M xfers */
+               if (r->rqtype == MEMTOMEM) {
+                       struct pl330_info *pi = xfer->chan->dmac->pi;
+                       int burst = 1 << ch->rqcfg.brst_size;
+                       u32 bytes = r->x->bytes;
+                       int bl;
+
+                       bl = pi->pcfg.data_bus_width / 8;
+                       bl *= pi->pcfg.data_buf_dep;
+                       bl /= burst;
+
+                       /* src/dst_burst_len can't be more than 16 */
+                       if (bl > 16)
+                               bl = 16;
+
+                       while (bl > 1) {
+                               if (!(bytes % (bl * burst)))
+                                       break;
+                               bl--;
+                       }
+
+                       ch->rqcfg.brst_len = bl;
+               } else {
+                       ch->rqcfg.brst_len = 1;
+               }
+
+               ret = pl330_submit_req(ch->pl330_chan_id, r);
+
+               /* If submission was successful */
+               if (!ret) {
+                       ch->lrq = r; /* latest submitted req */
+                       return 0;
+               }
+
+               r->x = NULL;
+
+               /* If both of the PL330 ping-pong buffers filled */
+               if (ret == -EAGAIN) {
+                       dev_err(ch->dmac->pi->dev, "%s:%d!\n",
+                               __func__, __LINE__);
+                       /* Queue back again */
+                       add_to_queue(ch, xfer, 1);
+                       ret = 0;
+               } else {
+                       dev_err(ch->dmac->pi->dev, "%s:%d!\n",
+                               __func__, __LINE__);
+                       _finish_off(xfer, S3C2410_RES_ERR, 0);
+               }
+       }
+
+       return ret;
+}
+
+static void s3c_pl330_rq(struct s3c_pl330_chan *ch,
+       struct pl330_req *r, enum pl330_op_err err)
+{
+       unsigned long flags;
+       struct s3c_pl330_xfer *xfer;
+       struct pl330_xfer *xl = r->x;
+       enum s3c2410_dma_buffresult res;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       r->x = NULL;
+
+       s3c_pl330_submit(ch, r);
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       /* Map result to S3C DMA API */
+       if (err == PL330_ERR_NONE)
+               res = S3C2410_RES_OK;
+       else if (err == PL330_ERR_ABORT)
+               res = S3C2410_RES_ABORT;
+       else
+               res = S3C2410_RES_ERR;
+
+       /* If last request had some xfer */
+       if (xl) {
+               xfer = container_of(xl, struct s3c_pl330_xfer, px);
+               _finish_off(xfer, res, 0);
+       } else {
+               dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
+                       __func__, __LINE__);
+       }
+}
+
+static void s3c_pl330_rq0(void *token, enum pl330_op_err err)
+{
+       struct pl330_req *r = token;
+       struct s3c_pl330_chan *ch = container_of(r,
+                                       struct s3c_pl330_chan, req[0]);
+       s3c_pl330_rq(ch, r, err);
+}
+
+static void s3c_pl330_rq1(void *token, enum pl330_op_err err)
+{
+       struct pl330_req *r = token;
+       struct s3c_pl330_chan *ch = container_of(r,
+                                       struct s3c_pl330_chan, req[1]);
+       s3c_pl330_rq(ch, r, err);
+}
+
+/* Release an acquired channel */
+static void chan_release(struct s3c_pl330_chan *ch)
+{
+       struct s3c_pl330_dmac *dmac;
+
+       if (chan_free(ch))
+               return;
+
+       dmac = ch->dmac;
+       ch->dmac = NULL;
+       dmac->busy_chan--;
+}
+
+int s3c2410_dma_ctrl(enum dma_ch id, enum s3c2410_chan_op op)
+{
+       struct s3c_pl330_xfer *xfer;
+       enum pl330_chan_op pl330op;
+       struct s3c_pl330_chan *ch;
+       unsigned long flags;
+       int idx, ret;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto ctrl_exit;
+       }
+
+       switch (op) {
+       case S3C2410_DMAOP_START:
+               /* Make sure both reqs are enqueued */
+               idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+               s3c_pl330_submit(ch, &ch->req[idx]);
+               s3c_pl330_submit(ch, &ch->req[1 - idx]);
+               pl330op = PL330_OP_START;
+               break;
+
+       case S3C2410_DMAOP_STOP:
+               pl330op = PL330_OP_ABORT;
+               break;
+
+       case S3C2410_DMAOP_FLUSH:
+               pl330op = PL330_OP_FLUSH;
+               break;
+
+       case S3C2410_DMAOP_PAUSE:
+       case S3C2410_DMAOP_RESUME:
+       case S3C2410_DMAOP_TIMEOUT:
+       case S3C2410_DMAOP_STARTED:
+               spin_unlock_irqrestore(&res_lock, flags);
+               return 0;
+
+       default:
+               spin_unlock_irqrestore(&res_lock, flags);
+               return -EINVAL;
+       }
+
+       ret = pl330_chan_ctrl(ch->pl330_chan_id, pl330op);
+
+       if (pl330op == PL330_OP_START) {
+               spin_unlock_irqrestore(&res_lock, flags);
+               return ret;
+       }
+
+       idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+
+       /* Abort the current xfer */
+       if (ch->req[idx].x) {
+               xfer = container_of(ch->req[idx].x,
+                               struct s3c_pl330_xfer, px);
+
+               /* Drop xfer during FLUSH */
+               if (pl330op == PL330_OP_FLUSH)
+                       del_from_queue(xfer);
+
+               ch->req[idx].x = NULL;
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, S3C2410_RES_ABORT,
+                               pl330op == PL330_OP_FLUSH ? 1 : 0);
+               spin_lock_irqsave(&res_lock, flags);
+       }
+
+       /* Flush the whole queue */
+       if (pl330op == PL330_OP_FLUSH) {
+
+               if (ch->req[1 - idx].x) {
+                       xfer = container_of(ch->req[1 - idx].x,
+                                       struct s3c_pl330_xfer, px);
+
+                       del_from_queue(xfer);
+
+                       ch->req[1 - idx].x = NULL;
+
+                       spin_unlock_irqrestore(&res_lock, flags);
+                       _finish_off(xfer, S3C2410_RES_ABORT, 1);
+                       spin_lock_irqsave(&res_lock, flags);
+               }
+
+               /* Finish off the remaining in the queue */
+               xfer = ch->xfer_head;
+               while (xfer) {
+
+                       del_from_queue(xfer);
+
+                       spin_unlock_irqrestore(&res_lock, flags);
+                       _finish_off(xfer, S3C2410_RES_ABORT, 1);
+                       spin_lock_irqsave(&res_lock, flags);
+
+                       xfer = ch->xfer_head;
+               }
+       }
+
+ctrl_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_ctrl);
+
+int s3c2410_dma_enqueue(enum dma_ch id, void *token,
+                       dma_addr_t addr, int size)
+{
+       struct s3c_pl330_chan *ch;
+       struct s3c_pl330_xfer *xfer;
+       unsigned long flags;
+       int idx, ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       /* Error if invalid or free channel */
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto enq_exit;
+       }
+
+       /* Error if size is unaligned */
+       if (ch->rqcfg.brst_size && size % (1 << ch->rqcfg.brst_size)) {
+               ret = -EINVAL;
+               goto enq_exit;
+       }
+
+       xfer = kmem_cache_alloc(ch->dmac->kmcache, GFP_ATOMIC);
+       if (!xfer) {
+               ret = -ENOMEM;
+               goto enq_exit;
+       }
+
+       xfer->token = token;
+       xfer->chan = ch;
+       xfer->px.bytes = size;
+       xfer->px.next = NULL; /* Single request */
+
+       /* For S3C DMA API, direction is always fixed for all xfers */
+       if (ch->req[0].rqtype == MEMTODEV) {
+               xfer->px.src_addr = addr;
+               xfer->px.dst_addr = ch->sdaddr;
+       } else {
+               xfer->px.src_addr = ch->sdaddr;
+               xfer->px.dst_addr = addr;
+       }
+
+       add_to_queue(ch, xfer, 0);
+
+       /* Try submitting on either request */
+       idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+
+       if (!ch->req[idx].x)
+               s3c_pl330_submit(ch, &ch->req[idx]);
+       else
+               s3c_pl330_submit(ch, &ch->req[1 - idx]);
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       if (ch->options & S3C2410_DMAF_AUTOSTART)
+               s3c2410_dma_ctrl(id, S3C2410_DMAOP_START);
+
+       return 0;
+
+enq_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_enqueue);
+
+int s3c2410_dma_request(enum dma_ch id,
+                       struct s3c2410_dma_client *client,
+                       void *dev)
+{
+       struct s3c_pl330_dmac *dmac;
+       struct s3c_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = chan_acquire(id);
+       if (!ch) {
+               ret = -EBUSY;
+               goto req_exit;
+       }
+
+       dmac = ch->dmac;
+
+       ch->pl330_chan_id = pl330_request_channel(dmac->pi);
+       if (!ch->pl330_chan_id) {
+               chan_release(ch);
+               ret = -EBUSY;
+               goto req_exit;
+       }
+
+       ch->client = client;
+       ch->options = 0; /* Clear any option */
+       ch->callback_fn = NULL; /* Clear any callback */
+       ch->lrq = NULL;
+
+       ch->rqcfg.brst_size = 2; /* Default word size */
+       ch->rqcfg.swap = SWAP_NO;
+       ch->rqcfg.scctl = SCCTRL0; /* Noncacheable and nonbufferable */
+       ch->rqcfg.dcctl = DCCTRL0; /* Noncacheable and nonbufferable */
+       ch->rqcfg.privileged = 0;
+       ch->rqcfg.insnaccess = 0;
+
+       /* Set invalid direction */
+       ch->req[0].rqtype = DEVTODEV;
+       ch->req[1].rqtype = ch->req[0].rqtype;
+
+       ch->req[0].cfg = &ch->rqcfg;
+       ch->req[1].cfg = ch->req[0].cfg;
+
+       ch->req[0].peri = iface_of_dmac(dmac, id) - 1; /* Original index */
+       ch->req[1].peri = ch->req[0].peri;
+
+       ch->req[0].token = &ch->req[0];
+       ch->req[0].xfer_cb = s3c_pl330_rq0;
+       ch->req[1].token = &ch->req[1];
+       ch->req[1].xfer_cb = s3c_pl330_rq1;
+
+       ch->req[0].x = NULL;
+       ch->req[1].x = NULL;
+
+       /* Reset xfer list */
+       INIT_LIST_HEAD(&ch->xfer_list);
+       ch->xfer_head = NULL;
+
+req_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_request);
+
+int s3c2410_dma_free(enum dma_ch id, struct s3c2410_dma_client *client)
+{
+       struct s3c_pl330_chan *ch;
+       struct s3c_pl330_xfer *xfer;
+       unsigned long flags;
+       int ret = 0;
+       unsigned idx;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch))
+               goto free_exit;
+
+       /* Refuse if someone else wanted to free the channel */
+       if (ch->client != client) {
+               ret = -EBUSY;
+               goto free_exit;
+       }
+
+       /* Stop any active xfer, Flushe the queue and do callbacks */
+       pl330_chan_ctrl(ch->pl330_chan_id, PL330_OP_FLUSH);
+
+       /* Abort the submitted requests */
+       idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
+
+       if (ch->req[idx].x) {
+               xfer = container_of(ch->req[idx].x,
+                               struct s3c_pl330_xfer, px);
+
+               ch->req[idx].x = NULL;
+               del_from_queue(xfer);
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, S3C2410_RES_ABORT, 1);
+               spin_lock_irqsave(&res_lock, flags);
+       }
+
+       if (ch->req[1 - idx].x) {
+               xfer = container_of(ch->req[1 - idx].x,
+                               struct s3c_pl330_xfer, px);
+
+               ch->req[1 - idx].x = NULL;
+               del_from_queue(xfer);
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, S3C2410_RES_ABORT, 1);
+               spin_lock_irqsave(&res_lock, flags);
+       }
+
+       /* Pluck and Abort the queued requests in order */
+       do {
+               xfer = get_from_queue(ch, 1);
+
+               spin_unlock_irqrestore(&res_lock, flags);
+               _finish_off(xfer, S3C2410_RES_ABORT, 1);
+               spin_lock_irqsave(&res_lock, flags);
+       } while (xfer);
+
+       ch->client = NULL;
+
+       pl330_release_channel(ch->pl330_chan_id);
+
+       ch->pl330_chan_id = NULL;
+
+       chan_release(ch);
+
+free_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_free);
+
+int s3c2410_dma_config(enum dma_ch id, int xferunit)
+{
+       struct s3c_pl330_chan *ch;
+       struct pl330_info *pi;
+       unsigned long flags;
+       int i, dbwidth, ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto cfg_exit;
+       }
+
+       pi = ch->dmac->pi;
+       dbwidth = pi->pcfg.data_bus_width / 8;
+
+       /* Max size of xfer can be pcfg.data_bus_width */
+       if (xferunit > dbwidth) {
+               ret = -EINVAL;
+               goto cfg_exit;
+       }
+
+       i = 0;
+       while (xferunit != (1 << i))
+               i++;
+
+       /* If valid value */
+       if (xferunit == (1 << i))
+               ch->rqcfg.brst_size = i;
+       else
+               ret = -EINVAL;
+
+cfg_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_config);
+
+/* Options that are supported by this driver */
+#define S3C_PL330_FLAGS (S3C2410_DMAF_CIRCULAR | S3C2410_DMAF_AUTOSTART)
+
+int s3c2410_dma_setflags(enum dma_ch id, unsigned int options)
+{
+       struct s3c_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch) || options & ~(S3C_PL330_FLAGS))
+               ret = -EINVAL;
+       else
+               ch->options = options;
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL(s3c2410_dma_setflags);
+
+int s3c2410_dma_set_buffdone_fn(enum dma_ch id, s3c2410_dma_cbfn_t rtn)
+{
+       struct s3c_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch))
+               ret = -EINVAL;
+       else
+               ch->callback_fn = rtn;
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);
+
+int s3c2410_dma_devconfig(enum dma_ch id, enum s3c2410_dmasrc source,
+                         unsigned long address)
+{
+       struct s3c_pl330_chan *ch;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       ch = id_to_chan(id);
+
+       if (!ch || chan_free(ch)) {
+               ret = -EINVAL;
+               goto devcfg_exit;
+       }
+
+       switch (source) {
+       case S3C2410_DMASRC_HW: /* P->M */
+               ch->req[0].rqtype = DEVTOMEM;
+               ch->req[1].rqtype = DEVTOMEM;
+               ch->rqcfg.src_inc = 0;
+               ch->rqcfg.dst_inc = 1;
+               break;
+       case S3C2410_DMASRC_MEM: /* M->P */
+               ch->req[0].rqtype = MEMTODEV;
+               ch->req[1].rqtype = MEMTODEV;
+               ch->rqcfg.src_inc = 1;
+               ch->rqcfg.dst_inc = 0;
+               break;
+       default:
+               ret = -EINVAL;
+               goto devcfg_exit;
+       }
+
+       ch->sdaddr = address;
+
+devcfg_exit:
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(s3c2410_dma_devconfig);
+
+int s3c2410_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
+{
+       struct s3c_pl330_chan *ch = id_to_chan(id);
+       struct pl330_chanstatus status;
+       int ret;
+
+       if (!ch || chan_free(ch))
+               return -EINVAL;
+
+       ret = pl330_chan_status(ch->pl330_chan_id, &status);
+       if (ret < 0)
+               return ret;
+
+       *src = status.src_addr;
+       *dst = status.dst_addr;
+
+       return 0;
+}
+EXPORT_SYMBOL(s3c2410_dma_getposition);
+
+static irqreturn_t pl330_irq_handler(int irq, void *data)
+{
+       if (pl330_update(data))
+               return IRQ_HANDLED;
+       else
+               return IRQ_NONE;
+}
+
+static int pl330_probe(struct platform_device *pdev)
+{
+       struct s3c_pl330_dmac *s3c_pl330_dmac;
+       struct s3c_pl330_platdata *pl330pd;
+       struct pl330_info *pl330_info;
+       struct resource *res;
+       int i, ret, irq;
+
+       pl330pd = pdev->dev.platform_data;
+
+       /* Can't do without the list of _32_ peripherals */
+       if (!pl330pd || !pl330pd->peri) {
+               dev_err(&pdev->dev, "platform data missing!\n");
+               return -ENODEV;
+       }
+
+       pl330_info = kzalloc(sizeof(*pl330_info), GFP_KERNEL);
+       if (!pl330_info)
+               return -ENOMEM;
+
+       pl330_info->pl330_data = NULL;
+       pl330_info->dev = &pdev->dev;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               ret = -ENODEV;
+               goto probe_err1;
+       }
+
+       request_mem_region(res->start, resource_size(res), pdev->name);
+
+       pl330_info->base = ioremap(res->start, resource_size(res));
+       if (!pl330_info->base) {
+               ret = -ENXIO;
+               goto probe_err2;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               ret = irq;
+               goto probe_err3;
+       }
+
+       ret = request_irq(irq, pl330_irq_handler, 0,
+                       dev_name(&pdev->dev), pl330_info);
+       if (ret)
+               goto probe_err4;
+
+       ret = pl330_add(pl330_info);
+       if (ret)
+               goto probe_err5;
+
+       /* Allocate a new DMAC */
+       s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL);
+       if (!s3c_pl330_dmac) {
+               ret = -ENOMEM;
+               goto probe_err6;
+       }
+
+       /* Hook the info */
+       s3c_pl330_dmac->pi = pl330_info;
+
+       /* No busy channels */
+       s3c_pl330_dmac->busy_chan = 0;
+
+       s3c_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
+                               sizeof(struct s3c_pl330_xfer), 0, 0, NULL);
+
+       if (!s3c_pl330_dmac->kmcache) {
+               ret = -ENOMEM;
+               goto probe_err7;
+       }
+
+       /* Get the list of peripherals */
+       s3c_pl330_dmac->peri = pl330pd->peri;
+
+       /* Attach to the list of DMACs */
+       list_add_tail(&s3c_pl330_dmac->node, &dmac_list);
+
+       /* Create a channel for each peripheral in the DMAC
+        * that is, if it doesn't already exist
+        */
+       for (i = 0; i < PL330_MAX_PERI; i++)
+               if (s3c_pl330_dmac->peri[i] != DMACH_MAX)
+                       chan_add(s3c_pl330_dmac->peri[i]);
+
+       printk(KERN_INFO
+               "Loaded driver for PL330 DMAC-%d %s\n", pdev->id, pdev->name);
+       printk(KERN_INFO
+               "\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n",
+               pl330_info->pcfg.data_buf_dep,
+               pl330_info->pcfg.data_bus_width / 8, pl330_info->pcfg.num_chan,
+               pl330_info->pcfg.num_peri, pl330_info->pcfg.num_events);
+
+       return 0;
+
+probe_err7:
+       kfree(s3c_pl330_dmac);
+probe_err6:
+       pl330_del(pl330_info);
+probe_err5:
+       free_irq(irq, pl330_info);
+probe_err4:
+probe_err3:
+       iounmap(pl330_info->base);
+probe_err2:
+       release_mem_region(res->start, resource_size(res));
+probe_err1:
+       kfree(pl330_info);
+
+       return ret;
+}
+
+static int pl330_remove(struct platform_device *pdev)
+{
+       struct s3c_pl330_dmac *dmac, *d;
+       struct s3c_pl330_chan *ch;
+       unsigned long flags;
+       int del, found;
+
+       if (!pdev->dev.platform_data)
+               return -EINVAL;
+
+       spin_lock_irqsave(&res_lock, flags);
+
+       found = 0;
+       list_for_each_entry(d, &dmac_list, node)
+               if (d->pi->dev == &pdev->dev) {
+                       found = 1;
+                       break;
+               }
+
+       if (!found) {
+               spin_unlock_irqrestore(&res_lock, flags);
+               return 0;
+       }
+
+       dmac = d;
+
+       /* Remove all Channels that are managed only by this DMAC */
+       list_for_each_entry(ch, &chan_list, node) {
+
+               /* Only channels that are handled by this DMAC */
+               if (iface_of_dmac(dmac, ch->id))
+                       del = 1;
+               else
+                       continue;
+
+               /* Don't remove if some other DMAC has it too */
+               list_for_each_entry(d, &dmac_list, node)
+                       if (d != dmac && iface_of_dmac(d, ch->id)) {
+                               del = 0;
+                               break;
+                       }
+
+               if (del) {
+                       spin_unlock_irqrestore(&res_lock, flags);
+                       s3c2410_dma_free(ch->id, ch->client);
+                       spin_lock_irqsave(&res_lock, flags);
+                       list_del(&ch->node);
+                       kfree(ch);
+               }
+       }
+
+       /* Remove the DMAC */
+       list_del(&dmac->node);
+       kfree(dmac);
+
+       spin_unlock_irqrestore(&res_lock, flags);
+
+       return 0;
+}
+
+static struct platform_driver pl330_driver = {
+       .driver         = {
+               .owner  = THIS_MODULE,
+               .name   = "s3c-pl330",
+       },
+       .probe          = pl330_probe,
+       .remove         = pl330_remove,
+};
+
+static int __init pl330_init(void)
+{
+       return platform_driver_register(&pl330_driver);
+}
+module_init(pl330_init);
+
+static void __exit pl330_exit(void)
+{
+       platform_driver_unregister(&pl330_driver);
+       return;
+}
+module_exit(pl330_exit);
+
+MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
+MODULE_LICENSE("GPL");