aboutsummaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorBen Dooks <ben-linux@fluff.org>2010-05-19 05:05:15 -0400
committerBen Dooks <ben-linux@fluff.org>2010-05-19 05:05:15 -0400
commit1e8ff636beb6ee5c87868a6081cc4f95b1ccd713 (patch)
tree5a6be34a53bf640c035d642b4d19388d6a7fd6cc /arch
parent32457942b90aabb9242b450f02d18d9c8d982916 (diff)
parent7d1a2077a7e519fc0c68617526abea3f72632e60 (diff)
ARM: Merge for-2635/s5p-dma
Merge branch 'for-2635/s5p-dma' into for-linus/samsung2 Conflicts: arch/arm/mach-s5pv210/Makefile
Diffstat (limited to 'arch')
-rw-r--r--arch/arm/mach-s5p6440/Kconfig1
-rw-r--r--arch/arm/mach-s5p6440/Makefile2
-rw-r--r--arch/arm/mach-s5p6440/dma.c105
-rw-r--r--arch/arm/mach-s5p6440/include/mach/dma.h26
-rw-r--r--arch/arm/mach-s5p6440/include/mach/map.h2
-rw-r--r--arch/arm/mach-s5p6442/Kconfig1
-rw-r--r--arch/arm/mach-s5p6442/Makefile2
-rw-r--r--arch/arm/mach-s5p6442/dma.c105
-rw-r--r--arch/arm/mach-s5p6442/include/mach/dma.h26
-rw-r--r--arch/arm/mach-s5p6442/include/mach/map.h3
-rw-r--r--arch/arm/mach-s5pv210/Kconfig1
-rw-r--r--arch/arm/mach-s5pv210/Makefile2
-rw-r--r--arch/arm/mach-s5pv210/dma.c168
-rw-r--r--arch/arm/mach-s5pv210/include/mach/dma.h26
-rw-r--r--arch/arm/mach-s5pv210/include/mach/map.h4
-rw-r--r--arch/arm/plat-samsung/Kconfig6
-rw-r--r--arch/arm/plat-samsung/Makefile2
-rw-r--r--arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h78
-rw-r--r--arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h32
-rw-r--r--arch/arm/plat-samsung/s3c-pl330.c1224
20 files changed, 1813 insertions, 3 deletions
diff --git a/arch/arm/mach-s5p6440/Kconfig b/arch/arm/mach-s5p6440/Kconfig
index 4c29ff8b07de..77aeffd17330 100644
--- a/arch/arm/mach-s5p6440/Kconfig
+++ b/arch/arm/mach-s5p6440/Kconfig
@@ -9,6 +9,7 @@ if ARCH_S5P6440
9 9
10config CPU_S5P6440 10config CPU_S5P6440
11 bool 11 bool
12 select S3C_PL330_DMA
12 help 13 help
13 Enable S5P6440 CPU support 14 Enable S5P6440 CPU support
14 15
diff --git a/arch/arm/mach-s5p6440/Makefile b/arch/arm/mach-s5p6440/Makefile
index 1ad894b1d3ab..3f243dd1ad14 100644
--- a/arch/arm/mach-s5p6440/Makefile
+++ b/arch/arm/mach-s5p6440/Makefile
@@ -12,7 +12,7 @@ obj- :=
12 12
13# Core support for S5P6440 system 13# Core support for S5P6440 system
14 14
15obj-$(CONFIG_CPU_S5P6440) += cpu.o init.o clock.o gpio.o 15obj-$(CONFIG_CPU_S5P6440) += cpu.o init.o clock.o gpio.o dma.o
16 16
17# machine support 17# machine support
18 18
diff --git a/arch/arm/mach-s5p6440/dma.c b/arch/arm/mach-s5p6440/dma.c
new file mode 100644
index 000000000000..07606ad57519
--- /dev/null
+++ b/arch/arm/mach-s5p6440/dma.c
@@ -0,0 +1,105 @@
1/*
2 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
3 * Jaswinder Singh <jassi.brar@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#include <linux/platform_device.h>
21#include <linux/dma-mapping.h>
22
23#include <plat/devs.h>
24#include <plat/irqs.h>
25
26#include <mach/map.h>
27#include <mach/irqs.h>
28
29#include <plat/s3c-pl330-pdata.h>
30
31static u64 dma_dmamask = DMA_BIT_MASK(32);
32
33static struct resource s5p6440_pdma_resource[] = {
34 [0] = {
35 .start = S5P6440_PA_PDMA,
36 .end = S5P6440_PA_PDMA + SZ_4K,
37 .flags = IORESOURCE_MEM,
38 },
39 [1] = {
40 .start = IRQ_DMA0,
41 .end = IRQ_DMA0,
42 .flags = IORESOURCE_IRQ,
43 },
44};
45
46static struct s3c_pl330_platdata s5p6440_pdma_pdata = {
47 .peri = {
48 [0] = DMACH_UART0_RX,
49 [1] = DMACH_UART0_TX,
50 [2] = DMACH_UART1_RX,
51 [3] = DMACH_UART1_TX,
52 [4] = DMACH_UART2_RX,
53 [5] = DMACH_UART2_TX,
54 [6] = DMACH_UART3_RX,
55 [7] = DMACH_UART3_TX,
56 [8] = DMACH_MAX,
57 [9] = DMACH_MAX,
58 [10] = DMACH_PCM0_TX,
59 [11] = DMACH_PCM0_RX,
60 [12] = DMACH_I2S0_TX,
61 [13] = DMACH_I2S0_RX,
62 [14] = DMACH_SPI0_TX,
63 [15] = DMACH_SPI0_RX,
64 [16] = DMACH_MAX,
65 [17] = DMACH_MAX,
66 [18] = DMACH_MAX,
67 [19] = DMACH_MAX,
68 [20] = DMACH_SPI1_TX,
69 [21] = DMACH_SPI1_RX,
70 [22] = DMACH_MAX,
71 [23] = DMACH_MAX,
72 [24] = DMACH_MAX,
73 [25] = DMACH_MAX,
74 [26] = DMACH_MAX,
75 [27] = DMACH_MAX,
76 [28] = DMACH_MAX,
77 [29] = DMACH_PWM,
78 [30] = DMACH_MAX,
79 [31] = DMACH_MAX,
80 },
81};
82
83static struct platform_device s5p6440_device_pdma = {
84 .name = "s3c-pl330",
85 .id = 1,
86 .num_resources = ARRAY_SIZE(s5p6440_pdma_resource),
87 .resource = s5p6440_pdma_resource,
88 .dev = {
89 .dma_mask = &dma_dmamask,
90 .coherent_dma_mask = DMA_BIT_MASK(32),
91 .platform_data = &s5p6440_pdma_pdata,
92 },
93};
94
95static struct platform_device *s5p6440_dmacs[] __initdata = {
96 &s5p6440_device_pdma,
97};
98
99static int __init s5p6440_dma_init(void)
100{
101 platform_add_devices(s5p6440_dmacs, ARRAY_SIZE(s5p6440_dmacs));
102
103 return 0;
104}
105arch_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
index 000000000000..81209eb1409b
--- /dev/null
+++ b/arch/arm/mach-s5p6440/include/mach/dma.h
@@ -0,0 +1,26 @@
1/*
2 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
3 * Jaswinder Singh <jassi.brar@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#ifndef __MACH_DMA_H
21#define __MACH_DMA_H
22
23/* This platform uses the common S3C DMA API driver for PL330 */
24#include <plat/s3c-dma-pl330.h>
25
26#endif /* __MACH_DMA_H */
diff --git a/arch/arm/mach-s5p6440/include/mach/map.h b/arch/arm/mach-s5p6440/include/mach/map.h
index 8924e5a4d6a6..0275784eb22f 100644
--- a/arch/arm/mach-s5p6440/include/mach/map.h
+++ b/arch/arm/mach-s5p6440/include/mach/map.h
@@ -29,6 +29,8 @@
29#define S5P6440_PA_VIC0 (0xE4000000) 29#define S5P6440_PA_VIC0 (0xE4000000)
30#define S5P_PA_VIC0 S5P6440_PA_VIC0 30#define S5P_PA_VIC0 S5P6440_PA_VIC0
31 31
32#define S5P6440_PA_PDMA 0xE9000000
33
32#define S5P6440_PA_VIC1 (0xE4100000) 34#define S5P6440_PA_VIC1 (0xE4100000)
33#define S5P_PA_VIC1 S5P6440_PA_VIC1 35#define S5P_PA_VIC1 S5P6440_PA_VIC1
34 36
diff --git a/arch/arm/mach-s5p6442/Kconfig b/arch/arm/mach-s5p6442/Kconfig
index 4f3f6de6a013..0fd41b447915 100644
--- a/arch/arm/mach-s5p6442/Kconfig
+++ b/arch/arm/mach-s5p6442/Kconfig
@@ -12,6 +12,7 @@ if ARCH_S5P6442
12config CPU_S5P6442 12config CPU_S5P6442
13 bool 13 bool
14 select PLAT_S5P 14 select PLAT_S5P
15 select S3C_PL330_DMA
15 help 16 help
16 Enable S5P6442 CPU support 17 Enable S5P6442 CPU support
17 18
diff --git a/arch/arm/mach-s5p6442/Makefile b/arch/arm/mach-s5p6442/Makefile
index dde39a6ce6bc..17bd22ee107d 100644
--- a/arch/arm/mach-s5p6442/Makefile
+++ b/arch/arm/mach-s5p6442/Makefile
@@ -12,7 +12,7 @@ obj- :=
12 12
13# Core support for S5P6442 system 13# Core support for S5P6442 system
14 14
15obj-$(CONFIG_CPU_S5P6442) += cpu.o init.o clock.o 15obj-$(CONFIG_CPU_S5P6442) += cpu.o init.o clock.o dma.o
16 16
17# machine support 17# machine support
18 18
diff --git a/arch/arm/mach-s5p6442/dma.c b/arch/arm/mach-s5p6442/dma.c
new file mode 100644
index 000000000000..ad4f8704b93d
--- /dev/null
+++ b/arch/arm/mach-s5p6442/dma.c
@@ -0,0 +1,105 @@
1/*
2 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
3 * Jaswinder Singh <jassi.brar@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#include <linux/platform_device.h>
21#include <linux/dma-mapping.h>
22
23#include <plat/devs.h>
24#include <plat/irqs.h>
25
26#include <mach/map.h>
27#include <mach/irqs.h>
28
29#include <plat/s3c-pl330-pdata.h>
30
31static u64 dma_dmamask = DMA_BIT_MASK(32);
32
33static struct resource s5p6442_pdma_resource[] = {
34 [0] = {
35 .start = S5P6442_PA_PDMA,
36 .end = S5P6442_PA_PDMA + SZ_4K,
37 .flags = IORESOURCE_MEM,
38 },
39 [1] = {
40 .start = IRQ_PDMA,
41 .end = IRQ_PDMA,
42 .flags = IORESOURCE_IRQ,
43 },
44};
45
46static struct s3c_pl330_platdata s5p6442_pdma_pdata = {
47 .peri = {
48 [0] = DMACH_UART0_RX,
49 [1] = DMACH_UART0_TX,
50 [2] = DMACH_UART1_RX,
51 [3] = DMACH_UART1_TX,
52 [4] = DMACH_UART2_RX,
53 [5] = DMACH_UART2_TX,
54 [6] = DMACH_MAX,
55 [7] = DMACH_MAX,
56 [8] = DMACH_MAX,
57 [9] = DMACH_I2S0_RX,
58 [10] = DMACH_I2S0_TX,
59 [11] = DMACH_I2S0S_TX,
60 [12] = DMACH_I2S1_RX,
61 [13] = DMACH_I2S1_TX,
62 [14] = DMACH_MAX,
63 [15] = DMACH_MAX,
64 [16] = DMACH_SPI0_RX,
65 [17] = DMACH_SPI0_TX,
66 [18] = DMACH_MAX,
67 [19] = DMACH_MAX,
68 [20] = DMACH_PCM0_RX,
69 [21] = DMACH_PCM0_TX,
70 [22] = DMACH_PCM1_RX,
71 [23] = DMACH_PCM1_TX,
72 [24] = DMACH_MAX,
73 [25] = DMACH_MAX,
74 [26] = DMACH_MAX,
75 [27] = DMACH_MSM_REQ0,
76 [28] = DMACH_MSM_REQ1,
77 [29] = DMACH_MSM_REQ2,
78 [30] = DMACH_MSM_REQ3,
79 [31] = DMACH_MAX,
80 },
81};
82
83static struct platform_device s5p6442_device_pdma = {
84 .name = "s3c-pl330",
85 .id = 1,
86 .num_resources = ARRAY_SIZE(s5p6442_pdma_resource),
87 .resource = s5p6442_pdma_resource,
88 .dev = {
89 .dma_mask = &dma_dmamask,
90 .coherent_dma_mask = DMA_BIT_MASK(32),
91 .platform_data = &s5p6442_pdma_pdata,
92 },
93};
94
95static struct platform_device *s5p6442_dmacs[] __initdata = {
96 &s5p6442_device_pdma,
97};
98
99static int __init s5p6442_dma_init(void)
100{
101 platform_add_devices(s5p6442_dmacs, ARRAY_SIZE(s5p6442_dmacs));
102
103 return 0;
104}
105arch_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
index 000000000000..81209eb1409b
--- /dev/null
+++ b/arch/arm/mach-s5p6442/include/mach/dma.h
@@ -0,0 +1,26 @@
1/*
2 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
3 * Jaswinder Singh <jassi.brar@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#ifndef __MACH_DMA_H
21#define __MACH_DMA_H
22
23/* This platform uses the common S3C DMA API driver for PL330 */
24#include <plat/s3c-dma-pl330.h>
25
26#endif /* __MACH_DMA_H */
diff --git a/arch/arm/mach-s5p6442/include/mach/map.h b/arch/arm/mach-s5p6442/include/mach/map.h
index 685277d792fb..a263d77f6968 100644
--- a/arch/arm/mach-s5p6442/include/mach/map.h
+++ b/arch/arm/mach-s5p6442/include/mach/map.h
@@ -34,6 +34,9 @@
34#define S5P6442_PA_VIC2 (0xE4200000) 34#define S5P6442_PA_VIC2 (0xE4200000)
35#define S5P_PA_VIC2 S5P6442_PA_VIC2 35#define S5P_PA_VIC2 S5P6442_PA_VIC2
36 36
37#define S5P6442_PA_MDMA 0xE8000000
38#define S5P6442_PA_PDMA 0xE9000000
39
37#define S5P6442_PA_TIMER (0xEA000000) 40#define S5P6442_PA_TIMER (0xEA000000)
38#define S5P_PA_TIMER S5P6442_PA_TIMER 41#define S5P_PA_TIMER S5P6442_PA_TIMER
39 42
diff --git a/arch/arm/mach-s5pv210/Kconfig b/arch/arm/mach-s5pv210/Kconfig
index af33a1a89b72..7601c28e240b 100644
--- a/arch/arm/mach-s5pv210/Kconfig
+++ b/arch/arm/mach-s5pv210/Kconfig
@@ -12,6 +12,7 @@ if ARCH_S5PV210
12config CPU_S5PV210 12config CPU_S5PV210
13 bool 13 bool
14 select PLAT_S5P 14 select PLAT_S5P
15 select S3C_PL330_DMA
15 help 16 help
16 Enable S5PV210 CPU support 17 Enable S5PV210 CPU support
17 18
diff --git a/arch/arm/mach-s5pv210/Makefile b/arch/arm/mach-s5pv210/Makefile
index 0acbdb34b560..192deac8aaab 100644
--- a/arch/arm/mach-s5pv210/Makefile
+++ b/arch/arm/mach-s5pv210/Makefile
@@ -12,7 +12,7 @@ obj- :=
12 12
13# Core support for S5PV210 system 13# Core support for S5PV210 system
14 14
15obj-$(CONFIG_CPU_S5PV210) += cpu.o init.o clock.o gpiolib.o 15obj-$(CONFIG_CPU_S5PV210) += cpu.o init.o clock.o dma.o gpiolib.o
16 16
17# machine support 17# machine support
18 18
diff --git a/arch/arm/mach-s5pv210/dma.c b/arch/arm/mach-s5pv210/dma.c
new file mode 100644
index 000000000000..778ad5fe231a
--- /dev/null
+++ b/arch/arm/mach-s5pv210/dma.c
@@ -0,0 +1,168 @@
1/*
2 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
3 * Jaswinder Singh <jassi.brar@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#include <linux/platform_device.h>
21#include <linux/dma-mapping.h>
22
23#include <plat/devs.h>
24#include <plat/irqs.h>
25
26#include <mach/map.h>
27#include <mach/irqs.h>
28
29#include <plat/s3c-pl330-pdata.h>
30
31static u64 dma_dmamask = DMA_BIT_MASK(32);
32
33static struct resource s5pv210_pdma0_resource[] = {
34 [0] = {
35 .start = S5PV210_PA_PDMA0,
36 .end = S5PV210_PA_PDMA0 + SZ_4K,
37 .flags = IORESOURCE_MEM,
38 },
39 [1] = {
40 .start = IRQ_PDMA0,
41 .end = IRQ_PDMA0,
42 .flags = IORESOURCE_IRQ,
43 },
44};
45
46static struct s3c_pl330_platdata s5pv210_pdma0_pdata = {
47 .peri = {
48 [0] = DMACH_UART0_RX,
49 [1] = DMACH_UART0_TX,
50 [2] = DMACH_UART1_RX,
51 [3] = DMACH_UART1_TX,
52 [4] = DMACH_UART2_RX,
53 [5] = DMACH_UART2_TX,
54 [6] = DMACH_UART3_RX,
55 [7] = DMACH_UART3_TX,
56 [8] = DMACH_MAX,
57 [9] = DMACH_I2S0_RX,
58 [10] = DMACH_I2S0_TX,
59 [11] = DMACH_I2S0S_TX,
60 [12] = DMACH_I2S1_RX,
61 [13] = DMACH_I2S1_TX,
62 [14] = DMACH_MAX,
63 [15] = DMACH_MAX,
64 [16] = DMACH_SPI0_RX,
65 [17] = DMACH_SPI0_TX,
66 [18] = DMACH_SPI1_RX,
67 [19] = DMACH_SPI1_TX,
68 [20] = DMACH_MAX,
69 [21] = DMACH_MAX,
70 [22] = DMACH_AC97_MICIN,
71 [23] = DMACH_AC97_PCMIN,
72 [24] = DMACH_AC97_PCMOUT,
73 [25] = DMACH_MAX,
74 [26] = DMACH_PWM,
75 [27] = DMACH_SPDIF,
76 [28] = DMACH_MAX,
77 [29] = DMACH_MAX,
78 [30] = DMACH_MAX,
79 [31] = DMACH_MAX,
80 },
81};
82
83static struct platform_device s5pv210_device_pdma0 = {
84 .name = "s3c-pl330",
85 .id = 1,
86 .num_resources = ARRAY_SIZE(s5pv210_pdma0_resource),
87 .resource = s5pv210_pdma0_resource,
88 .dev = {
89 .dma_mask = &dma_dmamask,
90 .coherent_dma_mask = DMA_BIT_MASK(32),
91 .platform_data = &s5pv210_pdma0_pdata,
92 },
93};
94
95static struct resource s5pv210_pdma1_resource[] = {
96 [0] = {
97 .start = S5PV210_PA_PDMA1,
98 .end = S5PV210_PA_PDMA1 + SZ_4K,
99 .flags = IORESOURCE_MEM,
100 },
101 [1] = {
102 .start = IRQ_PDMA1,
103 .end = IRQ_PDMA1,
104 .flags = IORESOURCE_IRQ,
105 },
106};
107
108static struct s3c_pl330_platdata s5pv210_pdma1_pdata = {
109 .peri = {
110 [0] = DMACH_UART0_RX,
111 [1] = DMACH_UART0_TX,
112 [2] = DMACH_UART1_RX,
113 [3] = DMACH_UART1_TX,
114 [4] = DMACH_UART2_RX,
115 [5] = DMACH_UART2_TX,
116 [6] = DMACH_UART3_RX,
117 [7] = DMACH_UART3_TX,
118 [8] = DMACH_MAX,
119 [9] = DMACH_I2S0_RX,
120 [10] = DMACH_I2S0_TX,
121 [11] = DMACH_I2S0S_TX,
122 [12] = DMACH_I2S1_RX,
123 [13] = DMACH_I2S1_TX,
124 [14] = DMACH_I2S2_RX,
125 [15] = DMACH_I2S2_TX,
126 [16] = DMACH_SPI0_RX,
127 [17] = DMACH_SPI0_TX,
128 [18] = DMACH_SPI1_RX,
129 [19] = DMACH_SPI1_TX,
130 [20] = DMACH_MAX,
131 [21] = DMACH_MAX,
132 [22] = DMACH_PCM0_RX,
133 [23] = DMACH_PCM0_TX,
134 [24] = DMACH_PCM1_RX,
135 [25] = DMACH_PCM1_TX,
136 [26] = DMACH_MSM_REQ0,
137 [27] = DMACH_MSM_REQ1,
138 [28] = DMACH_MSM_REQ2,
139 [29] = DMACH_MSM_REQ3,
140 [30] = DMACH_PCM2_RX,
141 [31] = DMACH_PCM2_TX,
142 },
143};
144
145static struct platform_device s5pv210_device_pdma1 = {
146 .name = "s3c-pl330",
147 .id = 2,
148 .num_resources = ARRAY_SIZE(s5pv210_pdma1_resource),
149 .resource = s5pv210_pdma1_resource,
150 .dev = {
151 .dma_mask = &dma_dmamask,
152 .coherent_dma_mask = DMA_BIT_MASK(32),
153 .platform_data = &s5pv210_pdma1_pdata,
154 },
155};
156
157static struct platform_device *s5pv210_dmacs[] __initdata = {
158 &s5pv210_device_pdma0,
159 &s5pv210_device_pdma1,
160};
161
162static int __init s5pv210_dma_init(void)
163{
164 platform_add_devices(s5pv210_dmacs, ARRAY_SIZE(s5pv210_dmacs));
165
166 return 0;
167}
168arch_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
index 000000000000..81209eb1409b
--- /dev/null
+++ b/arch/arm/mach-s5pv210/include/mach/dma.h
@@ -0,0 +1,26 @@
1/*
2 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
3 * Jaswinder Singh <jassi.brar@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#ifndef __MACH_DMA_H
21#define __MACH_DMA_H
22
23/* This platform uses the common S3C DMA API driver for PL330 */
24#include <plat/s3c-dma-pl330.h>
25
26#endif /* __MACH_DMA_H */
diff --git a/arch/arm/mach-s5pv210/include/mach/map.h b/arch/arm/mach-s5pv210/include/mach/map.h
index c22694c8231f..c038585080c0 100644
--- a/arch/arm/mach-s5pv210/include/mach/map.h
+++ b/arch/arm/mach-s5pv210/include/mach/map.h
@@ -43,6 +43,10 @@
43 43
44#define S5PV210_PA_SROMC (0xE8000000) 44#define S5PV210_PA_SROMC (0xE8000000)
45 45
46#define S5PV210_PA_MDMA 0xFA200000
47#define S5PV210_PA_PDMA0 0xE0900000
48#define S5PV210_PA_PDMA1 0xE0A00000
49
46#define S5PV210_PA_VIC0 (0xF2000000) 50#define S5PV210_PA_VIC0 (0xF2000000)
47#define S5P_PA_VIC0 S5PV210_PA_VIC0 51#define S5P_PA_VIC0 S5PV210_PA_VIC0
48 52
diff --git a/arch/arm/plat-samsung/Kconfig b/arch/arm/plat-samsung/Kconfig
index 7a36cf85e138..5349d2181bd2 100644
--- a/arch/arm/plat-samsung/Kconfig
+++ b/arch/arm/plat-samsung/Kconfig
@@ -203,6 +203,12 @@ config S3C_DMA
203 help 203 help
204 Internal configuration for S3C DMA core 204 Internal configuration for S3C DMA core
205 205
206config S3C_PL330_DMA
207 bool
208 select PL330
209 help
210 S3C DMA API Driver for PL330 DMAC.
211
206comment "Power management" 212comment "Power management"
207 213
208config SAMSUNG_PM_DEBUG 214config SAMSUNG_PM_DEBUG
diff --git a/arch/arm/plat-samsung/Makefile b/arch/arm/plat-samsung/Makefile
index 0ad820acc385..ca6036745edc 100644
--- a/arch/arm/plat-samsung/Makefile
+++ b/arch/arm/plat-samsung/Makefile
@@ -46,6 +46,8 @@ obj-$(CONFIG_S3C_DEV_NAND) += dev-nand.o
46 46
47obj-$(CONFIG_S3C_DMA) += dma.o 47obj-$(CONFIG_S3C_DMA) += dma.o
48 48
49obj-$(CONFIG_S3C_PL330_DMA) += s3c-pl330.o
50
49# PM support 51# PM support
50 52
51obj-$(CONFIG_PM) += pm.o 53obj-$(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
index 000000000000..5fe6721b57f7
--- /dev/null
+++ b/arch/arm/plat-samsung/include/plat/s3c-dma-pl330.h
@@ -0,0 +1,78 @@
1/*
2 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
3 * Jaswinder Singh <jassi.brar@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 */
10
11#ifndef __S3C_DMA_PL330_H_
12#define __S3C_DMA_PL330_H_
13
14#define S3C2410_DMAF_AUTOSTART (1 << 0)
15#define S3C2410_DMAF_CIRCULAR (1 << 1)
16
17/*
18 * PL330 can assign any channel to communicate with
19 * any of the peripherals attched to the DMAC.
20 * For the sake of consistency across client drivers,
21 * We keep the channel names unchanged and only add
22 * missing peripherals are added.
23 * Order is not important since S3C PL330 API driver
24 * use these just as IDs.
25 */
26enum dma_ch {
27 DMACH_UART0_RX,
28 DMACH_UART0_TX,
29 DMACH_UART1_RX,
30 DMACH_UART1_TX,
31 DMACH_UART2_RX,
32 DMACH_UART2_TX,
33 DMACH_UART3_RX,
34 DMACH_UART3_TX,
35 DMACH_IRDA,
36 DMACH_I2S0_RX,
37 DMACH_I2S0_TX,
38 DMACH_I2S0S_TX,
39 DMACH_I2S1_RX,
40 DMACH_I2S1_TX,
41 DMACH_I2S2_RX,
42 DMACH_I2S2_TX,
43 DMACH_SPI0_RX,
44 DMACH_SPI0_TX,
45 DMACH_SPI1_RX,
46 DMACH_SPI1_TX,
47 DMACH_SPI2_RX,
48 DMACH_SPI2_TX,
49 DMACH_AC97_MICIN,
50 DMACH_AC97_PCMIN,
51 DMACH_AC97_PCMOUT,
52 DMACH_EXTERNAL,
53 DMACH_PWM,
54 DMACH_SPDIF,
55 DMACH_HSI_RX,
56 DMACH_HSI_TX,
57 DMACH_PCM0_TX,
58 DMACH_PCM0_RX,
59 DMACH_PCM1_TX,
60 DMACH_PCM1_RX,
61 DMACH_PCM2_TX,
62 DMACH_PCM2_RX,
63 DMACH_MSM_REQ3,
64 DMACH_MSM_REQ2,
65 DMACH_MSM_REQ1,
66 DMACH_MSM_REQ0,
67 /* END Marker, also used to denote a reserved channel */
68 DMACH_MAX,
69};
70
71static inline bool s3c_dma_has_circular(void)
72{
73 return true;
74}
75
76#include <plat/dma.h>
77
78#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
index 000000000000..bf5e2a9d408d
--- /dev/null
+++ b/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h
@@ -0,0 +1,32 @@
1/* linux/arch/arm/plat-samsung/include/plat/s3c-pl330-pdata.h
2 *
3 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
4 * Jaswinder Singh <jassi.brar@samsung.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 */
11
12#ifndef __S3C_PL330_PDATA_H
13#define __S3C_PL330_PDATA_H
14
15#include <plat/s3c-dma-pl330.h>
16
17/*
18 * Every PL330 DMAC has max 32 peripheral interfaces,
19 * of which some may be not be really used in your
20 * DMAC's configuration.
21 * Populate this array of 32 peri i/fs with relevant
22 * channel IDs for used peri i/f and DMACH_MAX for
23 * those unused.
24 *
25 * The platforms just need to provide this info
26 * to the S3C DMA API driver for PL330.
27 */
28struct s3c_pl330_platdata {
29 enum dma_ch peri[32];
30};
31
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
index 000000000000..a91305a60aed
--- /dev/null
+++ b/arch/arm/plat-samsung/s3c-pl330.c
@@ -0,0 +1,1224 @@
1/* linux/arch/arm/plat-samsung/s3c-pl330.c
2 *
3 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
4 * Jaswinder Singh <jassi.brar@samsung.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 */
11
12#include <linux/init.h>
13#include <linux/module.h>
14#include <linux/interrupt.h>
15#include <linux/io.h>
16#include <linux/slab.h>
17#include <linux/platform_device.h>
18
19#include <asm/hardware/pl330.h>
20
21#include <plat/s3c-pl330-pdata.h>
22
23/**
24 * struct s3c_pl330_dmac - Logical representation of a PL330 DMAC.
25 * @busy_chan: Number of channels currently busy.
26 * @peri: List of IDs of peripherals this DMAC can work with.
27 * @node: To attach to the global list of DMACs.
28 * @pi: PL330 configuration info for the DMAC.
29 * @kmcache: Pool to quickly allocate xfers for all channels in the dmac.
30 */
31struct s3c_pl330_dmac {
32 unsigned busy_chan;
33 enum dma_ch *peri;
34 struct list_head node;
35 struct pl330_info *pi;
36 struct kmem_cache *kmcache;
37};
38
39/**
40 * struct s3c_pl330_xfer - A request submitted by S3C DMA clients.
41 * @token: Xfer ID provided by the client.
42 * @node: To attach to the list of xfers on a channel.
43 * @px: Xfer for PL330 core.
44 * @chan: Owner channel of this xfer.
45 */
46struct s3c_pl330_xfer {
47 void *token;
48 struct list_head node;
49 struct pl330_xfer px;
50 struct s3c_pl330_chan *chan;
51};
52
53/**
54 * struct s3c_pl330_chan - Logical channel to communicate with
55 * a Physical peripheral.
56 * @pl330_chan_id: Token of a hardware channel thread of PL330 DMAC.
57 * NULL if the channel is available to be acquired.
58 * @id: ID of the peripheral that this channel can communicate with.
59 * @options: Options specified by the client.
60 * @sdaddr: Address provided via s3c2410_dma_devconfig.
61 * @node: To attach to the global list of channels.
62 * @lrq: Pointer to the last submitted pl330_req to PL330 core.
63 * @xfer_list: To manage list of xfers enqueued.
64 * @req: Two requests to communicate with the PL330 engine.
65 * @callback_fn: Callback function to the client.
66 * @rqcfg: Channel configuration for the xfers.
67 * @xfer_head: Pointer to the xfer to be next excecuted.
68 * @dmac: Pointer to the DMAC that manages this channel, NULL if the
69 * channel is available to be acquired.
70 * @client: Client of this channel. NULL if the
71 * channel is available to be acquired.
72 */
73struct s3c_pl330_chan {
74 void *pl330_chan_id;
75 enum dma_ch id;
76 unsigned int options;
77 unsigned long sdaddr;
78 struct list_head node;
79 struct pl330_req *lrq;
80 struct list_head xfer_list;
81 struct pl330_req req[2];
82 s3c2410_dma_cbfn_t callback_fn;
83 struct pl330_reqcfg rqcfg;
84 struct s3c_pl330_xfer *xfer_head;
85 struct s3c_pl330_dmac *dmac;
86 struct s3c2410_dma_client *client;
87};
88
89/* All DMACs in the platform */
90static LIST_HEAD(dmac_list);
91
92/* All channels to peripherals in the platform */
93static LIST_HEAD(chan_list);
94
95/*
96 * Since we add resources(DMACs and Channels) to the global pool,
97 * we need to guard access to the resources using a global lock
98 */
99static DEFINE_SPINLOCK(res_lock);
100
101/* Returns the channel with ID 'id' in the chan_list */
102static struct s3c_pl330_chan *id_to_chan(const enum dma_ch id)
103{
104 struct s3c_pl330_chan *ch;
105
106 list_for_each_entry(ch, &chan_list, node)
107 if (ch->id == id)
108 return ch;
109
110 return NULL;
111}
112
113/* Allocate a new channel with ID 'id' and add to chan_list */
114static void chan_add(const enum dma_ch id)
115{
116 struct s3c_pl330_chan *ch = id_to_chan(id);
117
118 /* Return if the channel already exists */
119 if (ch)
120 return;
121
122 ch = kmalloc(sizeof(*ch), GFP_KERNEL);
123 /* Return silently to work with other channels */
124 if (!ch)
125 return;
126
127 ch->id = id;
128 ch->dmac = NULL;
129
130 list_add_tail(&ch->node, &chan_list);
131}
132
133/* If the channel is not yet acquired by any client */
134static bool chan_free(struct s3c_pl330_chan *ch)
135{
136 if (!ch)
137 return false;
138
139 /* Channel points to some DMAC only when it's acquired */
140 return ch->dmac ? false : true;
141}
142
143/*
144 * Returns 0 is peripheral i/f is invalid or not present on the dmac.
145 * Index + 1, otherwise.
146 */
147static unsigned iface_of_dmac(struct s3c_pl330_dmac *dmac, enum dma_ch ch_id)
148{
149 enum dma_ch *id = dmac->peri;
150 int i;
151
152 /* Discount invalid markers */
153 if (ch_id == DMACH_MAX)
154 return 0;
155
156 for (i = 0; i < PL330_MAX_PERI; i++)
157 if (id[i] == ch_id)
158 return i + 1;
159
160 return 0;
161}
162
163/* If all channel threads of the DMAC are busy */
164static inline bool dmac_busy(struct s3c_pl330_dmac *dmac)
165{
166 struct pl330_info *pi = dmac->pi;
167
168 return (dmac->busy_chan < pi->pcfg.num_chan) ? false : true;
169}
170
171/*
172 * Returns the number of free channels that
173 * can be handled by this dmac only.
174 */
175static unsigned ch_onlyby_dmac(struct s3c_pl330_dmac *dmac)
176{
177 enum dma_ch *id = dmac->peri;
178 struct s3c_pl330_dmac *d;
179 struct s3c_pl330_chan *ch;
180 unsigned found, count = 0;
181 enum dma_ch p;
182 int i;
183
184 for (i = 0; i < PL330_MAX_PERI; i++) {
185 p = id[i];
186 ch = id_to_chan(p);
187
188 if (p == DMACH_MAX || !chan_free(ch))
189 continue;
190
191 found = 0;
192 list_for_each_entry(d, &dmac_list, node) {
193 if (d != dmac && iface_of_dmac(d, ch->id)) {
194 found = 1;
195 break;
196 }
197 }
198 if (!found)
199 count++;
200 }
201
202 return count;
203}
204
205/*
206 * Measure of suitability of 'dmac' handling 'ch'
207 *
208 * 0 indicates 'dmac' can not handle 'ch' either
209 * because it is not supported by the hardware or
210 * because all dmac channels are currently busy.
211 *
212 * >0 vlaue indicates 'dmac' has the capability.
213 * The bigger the value the more suitable the dmac.
214 */
215#define MAX_SUIT UINT_MAX
216#define MIN_SUIT 0
217
218static unsigned suitablility(struct s3c_pl330_dmac *dmac,
219 struct s3c_pl330_chan *ch)
220{
221 struct pl330_info *pi = dmac->pi;
222 enum dma_ch *id = dmac->peri;
223 struct s3c_pl330_dmac *d;
224 unsigned s;
225 int i;
226
227 s = MIN_SUIT;
228 /* If all the DMAC channel threads are busy */
229 if (dmac_busy(dmac))
230 return s;
231
232 for (i = 0; i < PL330_MAX_PERI; i++)
233 if (id[i] == ch->id)
234 break;
235
236 /* If the 'dmac' can't talk to 'ch' */
237 if (i == PL330_MAX_PERI)
238 return s;
239
240 s = MAX_SUIT;
241 list_for_each_entry(d, &dmac_list, node) {
242 /*
243 * If some other dmac can talk to this
244 * peri and has some channel free.
245 */
246 if (d != dmac && iface_of_dmac(d, ch->id) && !dmac_busy(d)) {
247 s = 0;
248 break;
249 }
250 }
251 if (s)
252 return s;
253
254 s = 100;
255
256 /* Good if free chans are more, bad otherwise */
257 s += (pi->pcfg.num_chan - dmac->busy_chan) - ch_onlyby_dmac(dmac);
258
259 return s;
260}
261
262/* More than one DMAC may have capability to transfer data with the
263 * peripheral. This function assigns most suitable DMAC to manage the
264 * channel and hence communicate with the peripheral.
265 */
266static struct s3c_pl330_dmac *map_chan_to_dmac(struct s3c_pl330_chan *ch)
267{
268 struct s3c_pl330_dmac *d, *dmac = NULL;
269 unsigned sn, sl = MIN_SUIT;
270
271 list_for_each_entry(d, &dmac_list, node) {
272 sn = suitablility(d, ch);
273
274 if (sn == MAX_SUIT)
275 return d;
276
277 if (sn > sl)
278 dmac = d;
279 }
280
281 return dmac;
282}
283
284/* Acquire the channel for peripheral 'id' */
285static struct s3c_pl330_chan *chan_acquire(const enum dma_ch id)
286{
287 struct s3c_pl330_chan *ch = id_to_chan(id);
288 struct s3c_pl330_dmac *dmac;
289
290 /* If the channel doesn't exist or is already acquired */
291 if (!ch || !chan_free(ch)) {
292 ch = NULL;
293 goto acq_exit;
294 }
295
296 dmac = map_chan_to_dmac(ch);
297 /* If couldn't map */
298 if (!dmac) {
299 ch = NULL;
300 goto acq_exit;
301 }
302
303 dmac->busy_chan++;
304 ch->dmac = dmac;
305
306acq_exit:
307 return ch;
308}
309
310/* Delete xfer from the queue */
311static inline void del_from_queue(struct s3c_pl330_xfer *xfer)
312{
313 struct s3c_pl330_xfer *t;
314 struct s3c_pl330_chan *ch;
315 int found;
316
317 if (!xfer)
318 return;
319
320 ch = xfer->chan;
321
322 /* Make sure xfer is in the queue */
323 found = 0;
324 list_for_each_entry(t, &ch->xfer_list, node)
325 if (t == xfer) {
326 found = 1;
327 break;
328 }
329
330 if (!found)
331 return;
332
333 /* If xfer is last entry in the queue */
334 if (xfer->node.next == &ch->xfer_list)
335 t = list_entry(ch->xfer_list.next,
336 struct s3c_pl330_xfer, node);
337 else
338 t = list_entry(xfer->node.next,
339 struct s3c_pl330_xfer, node);
340
341 /* If there was only one node left */
342 if (t == xfer)
343 ch->xfer_head = NULL;
344 else if (ch->xfer_head == xfer)
345 ch->xfer_head = t;
346
347 list_del(&xfer->node);
348}
349
350/* Provides pointer to the next xfer in the queue.
351 * If CIRCULAR option is set, the list is left intact,
352 * otherwise the xfer is removed from the list.
353 * Forced delete 'pluck' can be set to override the CIRCULAR option.
354 */
355static struct s3c_pl330_xfer *get_from_queue(struct s3c_pl330_chan *ch,
356 int pluck)
357{
358 struct s3c_pl330_xfer *xfer = ch->xfer_head;
359
360 if (!xfer)
361 return NULL;
362
363 /* If xfer is last entry in the queue */
364 if (xfer->node.next == &ch->xfer_list)
365 ch->xfer_head = list_entry(ch->xfer_list.next,
366 struct s3c_pl330_xfer, node);
367 else
368 ch->xfer_head = list_entry(xfer->node.next,
369 struct s3c_pl330_xfer, node);
370
371 if (pluck || !(ch->options & S3C2410_DMAF_CIRCULAR))
372 del_from_queue(xfer);
373
374 return xfer;
375}
376
377static inline void add_to_queue(struct s3c_pl330_chan *ch,
378 struct s3c_pl330_xfer *xfer, int front)
379{
380 struct pl330_xfer *xt;
381
382 /* If queue empty */
383 if (ch->xfer_head == NULL)
384 ch->xfer_head = xfer;
385
386 xt = &ch->xfer_head->px;
387 /* If the head already submitted (CIRCULAR head) */
388 if (ch->options & S3C2410_DMAF_CIRCULAR &&
389 (xt == ch->req[0].x || xt == ch->req[1].x))
390 ch->xfer_head = xfer;
391
392 /* If this is a resubmission, it should go at the head */
393 if (front) {
394 ch->xfer_head = xfer;
395 list_add(&xfer->node, &ch->xfer_list);
396 } else {
397 list_add_tail(&xfer->node, &ch->xfer_list);
398 }
399}
400
401static inline void _finish_off(struct s3c_pl330_xfer *xfer,
402 enum s3c2410_dma_buffresult res, int ffree)
403{
404 struct s3c_pl330_chan *ch;
405
406 if (!xfer)
407 return;
408
409 ch = xfer->chan;
410
411 /* Do callback */
412 if (ch->callback_fn)
413 ch->callback_fn(NULL, xfer->token, xfer->px.bytes, res);
414
415 /* Force Free or if buffer is not needed anymore */
416 if (ffree || !(ch->options & S3C2410_DMAF_CIRCULAR))
417 kmem_cache_free(ch->dmac->kmcache, xfer);
418}
419
420static inline int s3c_pl330_submit(struct s3c_pl330_chan *ch,
421 struct pl330_req *r)
422{
423 struct s3c_pl330_xfer *xfer;
424 int ret = 0;
425
426 /* If already submitted */
427 if (r->x)
428 return 0;
429
430 xfer = get_from_queue(ch, 0);
431 if (xfer) {
432 r->x = &xfer->px;
433
434 /* Use max bandwidth for M<->M xfers */
435 if (r->rqtype == MEMTOMEM) {
436 struct pl330_info *pi = xfer->chan->dmac->pi;
437 int burst = 1 << ch->rqcfg.brst_size;
438 u32 bytes = r->x->bytes;
439 int bl;
440
441 bl = pi->pcfg.data_bus_width / 8;
442 bl *= pi->pcfg.data_buf_dep;
443 bl /= burst;
444
445 /* src/dst_burst_len can't be more than 16 */
446 if (bl > 16)
447 bl = 16;
448
449 while (bl > 1) {
450 if (!(bytes % (bl * burst)))
451 break;
452 bl--;
453 }
454
455 ch->rqcfg.brst_len = bl;
456 } else {
457 ch->rqcfg.brst_len = 1;
458 }
459
460 ret = pl330_submit_req(ch->pl330_chan_id, r);
461
462 /* If submission was successful */
463 if (!ret) {
464 ch->lrq = r; /* latest submitted req */
465 return 0;
466 }
467
468 r->x = NULL;
469
470 /* If both of the PL330 ping-pong buffers filled */
471 if (ret == -EAGAIN) {
472 dev_err(ch->dmac->pi->dev, "%s:%d!\n",
473 __func__, __LINE__);
474 /* Queue back again */
475 add_to_queue(ch, xfer, 1);
476 ret = 0;
477 } else {
478 dev_err(ch->dmac->pi->dev, "%s:%d!\n",
479 __func__, __LINE__);
480 _finish_off(xfer, S3C2410_RES_ERR, 0);
481 }
482 }
483
484 return ret;
485}
486
487static void s3c_pl330_rq(struct s3c_pl330_chan *ch,
488 struct pl330_req *r, enum pl330_op_err err)
489{
490 unsigned long flags;
491 struct s3c_pl330_xfer *xfer;
492 struct pl330_xfer *xl = r->x;
493 enum s3c2410_dma_buffresult res;
494
495 spin_lock_irqsave(&res_lock, flags);
496
497 r->x = NULL;
498
499 s3c_pl330_submit(ch, r);
500
501 spin_unlock_irqrestore(&res_lock, flags);
502
503 /* Map result to S3C DMA API */
504 if (err == PL330_ERR_NONE)
505 res = S3C2410_RES_OK;
506 else if (err == PL330_ERR_ABORT)
507 res = S3C2410_RES_ABORT;
508 else
509 res = S3C2410_RES_ERR;
510
511 /* If last request had some xfer */
512 if (xl) {
513 xfer = container_of(xl, struct s3c_pl330_xfer, px);
514 _finish_off(xfer, res, 0);
515 } else {
516 dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
517 __func__, __LINE__);
518 }
519}
520
521static void s3c_pl330_rq0(void *token, enum pl330_op_err err)
522{
523 struct pl330_req *r = token;
524 struct s3c_pl330_chan *ch = container_of(r,
525 struct s3c_pl330_chan, req[0]);
526 s3c_pl330_rq(ch, r, err);
527}
528
529static void s3c_pl330_rq1(void *token, enum pl330_op_err err)
530{
531 struct pl330_req *r = token;
532 struct s3c_pl330_chan *ch = container_of(r,
533 struct s3c_pl330_chan, req[1]);
534 s3c_pl330_rq(ch, r, err);
535}
536
537/* Release an acquired channel */
538static void chan_release(struct s3c_pl330_chan *ch)
539{
540 struct s3c_pl330_dmac *dmac;
541
542 if (chan_free(ch))
543 return;
544
545 dmac = ch->dmac;
546 ch->dmac = NULL;
547 dmac->busy_chan--;
548}
549
550int s3c2410_dma_ctrl(enum dma_ch id, enum s3c2410_chan_op op)
551{
552 struct s3c_pl330_xfer *xfer;
553 enum pl330_chan_op pl330op;
554 struct s3c_pl330_chan *ch;
555 unsigned long flags;
556 int idx, ret;
557
558 spin_lock_irqsave(&res_lock, flags);
559
560 ch = id_to_chan(id);
561
562 if (!ch || chan_free(ch)) {
563 ret = -EINVAL;
564 goto ctrl_exit;
565 }
566
567 switch (op) {
568 case S3C2410_DMAOP_START:
569 /* Make sure both reqs are enqueued */
570 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
571 s3c_pl330_submit(ch, &ch->req[idx]);
572 s3c_pl330_submit(ch, &ch->req[1 - idx]);
573 pl330op = PL330_OP_START;
574 break;
575
576 case S3C2410_DMAOP_STOP:
577 pl330op = PL330_OP_ABORT;
578 break;
579
580 case S3C2410_DMAOP_FLUSH:
581 pl330op = PL330_OP_FLUSH;
582 break;
583
584 case S3C2410_DMAOP_PAUSE:
585 case S3C2410_DMAOP_RESUME:
586 case S3C2410_DMAOP_TIMEOUT:
587 case S3C2410_DMAOP_STARTED:
588 spin_unlock_irqrestore(&res_lock, flags);
589 return 0;
590
591 default:
592 spin_unlock_irqrestore(&res_lock, flags);
593 return -EINVAL;
594 }
595
596 ret = pl330_chan_ctrl(ch->pl330_chan_id, pl330op);
597
598 if (pl330op == PL330_OP_START) {
599 spin_unlock_irqrestore(&res_lock, flags);
600 return ret;
601 }
602
603 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
604
605 /* Abort the current xfer */
606 if (ch->req[idx].x) {
607 xfer = container_of(ch->req[idx].x,
608 struct s3c_pl330_xfer, px);
609
610 /* Drop xfer during FLUSH */
611 if (pl330op == PL330_OP_FLUSH)
612 del_from_queue(xfer);
613
614 ch->req[idx].x = NULL;
615
616 spin_unlock_irqrestore(&res_lock, flags);
617 _finish_off(xfer, S3C2410_RES_ABORT,
618 pl330op == PL330_OP_FLUSH ? 1 : 0);
619 spin_lock_irqsave(&res_lock, flags);
620 }
621
622 /* Flush the whole queue */
623 if (pl330op == PL330_OP_FLUSH) {
624
625 if (ch->req[1 - idx].x) {
626 xfer = container_of(ch->req[1 - idx].x,
627 struct s3c_pl330_xfer, px);
628
629 del_from_queue(xfer);
630
631 ch->req[1 - idx].x = NULL;
632
633 spin_unlock_irqrestore(&res_lock, flags);
634 _finish_off(xfer, S3C2410_RES_ABORT, 1);
635 spin_lock_irqsave(&res_lock, flags);
636 }
637
638 /* Finish off the remaining in the queue */
639 xfer = ch->xfer_head;
640 while (xfer) {
641
642 del_from_queue(xfer);
643
644 spin_unlock_irqrestore(&res_lock, flags);
645 _finish_off(xfer, S3C2410_RES_ABORT, 1);
646 spin_lock_irqsave(&res_lock, flags);
647
648 xfer = ch->xfer_head;
649 }
650 }
651
652ctrl_exit:
653 spin_unlock_irqrestore(&res_lock, flags);
654
655 return ret;
656}
657EXPORT_SYMBOL(s3c2410_dma_ctrl);
658
659int s3c2410_dma_enqueue(enum dma_ch id, void *token,
660 dma_addr_t addr, int size)
661{
662 struct s3c_pl330_chan *ch;
663 struct s3c_pl330_xfer *xfer;
664 unsigned long flags;
665 int idx, ret = 0;
666
667 spin_lock_irqsave(&res_lock, flags);
668
669 ch = id_to_chan(id);
670
671 /* Error if invalid or free channel */
672 if (!ch || chan_free(ch)) {
673 ret = -EINVAL;
674 goto enq_exit;
675 }
676
677 /* Error if size is unaligned */
678 if (ch->rqcfg.brst_size && size % (1 << ch->rqcfg.brst_size)) {
679 ret = -EINVAL;
680 goto enq_exit;
681 }
682
683 xfer = kmem_cache_alloc(ch->dmac->kmcache, GFP_ATOMIC);
684 if (!xfer) {
685 ret = -ENOMEM;
686 goto enq_exit;
687 }
688
689 xfer->token = token;
690 xfer->chan = ch;
691 xfer->px.bytes = size;
692 xfer->px.next = NULL; /* Single request */
693
694 /* For S3C DMA API, direction is always fixed for all xfers */
695 if (ch->req[0].rqtype == MEMTODEV) {
696 xfer->px.src_addr = addr;
697 xfer->px.dst_addr = ch->sdaddr;
698 } else {
699 xfer->px.src_addr = ch->sdaddr;
700 xfer->px.dst_addr = addr;
701 }
702
703 add_to_queue(ch, xfer, 0);
704
705 /* Try submitting on either request */
706 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
707
708 if (!ch->req[idx].x)
709 s3c_pl330_submit(ch, &ch->req[idx]);
710 else
711 s3c_pl330_submit(ch, &ch->req[1 - idx]);
712
713 spin_unlock_irqrestore(&res_lock, flags);
714
715 if (ch->options & S3C2410_DMAF_AUTOSTART)
716 s3c2410_dma_ctrl(id, S3C2410_DMAOP_START);
717
718 return 0;
719
720enq_exit:
721 spin_unlock_irqrestore(&res_lock, flags);
722
723 return ret;
724}
725EXPORT_SYMBOL(s3c2410_dma_enqueue);
726
727int s3c2410_dma_request(enum dma_ch id,
728 struct s3c2410_dma_client *client,
729 void *dev)
730{
731 struct s3c_pl330_dmac *dmac;
732 struct s3c_pl330_chan *ch;
733 unsigned long flags;
734 int ret = 0;
735
736 spin_lock_irqsave(&res_lock, flags);
737
738 ch = chan_acquire(id);
739 if (!ch) {
740 ret = -EBUSY;
741 goto req_exit;
742 }
743
744 dmac = ch->dmac;
745
746 ch->pl330_chan_id = pl330_request_channel(dmac->pi);
747 if (!ch->pl330_chan_id) {
748 chan_release(ch);
749 ret = -EBUSY;
750 goto req_exit;
751 }
752
753 ch->client = client;
754 ch->options = 0; /* Clear any option */
755 ch->callback_fn = NULL; /* Clear any callback */
756 ch->lrq = NULL;
757
758 ch->rqcfg.brst_size = 2; /* Default word size */
759 ch->rqcfg.swap = SWAP_NO;
760 ch->rqcfg.scctl = SCCTRL0; /* Noncacheable and nonbufferable */
761 ch->rqcfg.dcctl = DCCTRL0; /* Noncacheable and nonbufferable */
762 ch->rqcfg.privileged = 0;
763 ch->rqcfg.insnaccess = 0;
764
765 /* Set invalid direction */
766 ch->req[0].rqtype = DEVTODEV;
767 ch->req[1].rqtype = ch->req[0].rqtype;
768
769 ch->req[0].cfg = &ch->rqcfg;
770 ch->req[1].cfg = ch->req[0].cfg;
771
772 ch->req[0].peri = iface_of_dmac(dmac, id) - 1; /* Original index */
773 ch->req[1].peri = ch->req[0].peri;
774
775 ch->req[0].token = &ch->req[0];
776 ch->req[0].xfer_cb = s3c_pl330_rq0;
777 ch->req[1].token = &ch->req[1];
778 ch->req[1].xfer_cb = s3c_pl330_rq1;
779
780 ch->req[0].x = NULL;
781 ch->req[1].x = NULL;
782
783 /* Reset xfer list */
784 INIT_LIST_HEAD(&ch->xfer_list);
785 ch->xfer_head = NULL;
786
787req_exit:
788 spin_unlock_irqrestore(&res_lock, flags);
789
790 return ret;
791}
792EXPORT_SYMBOL(s3c2410_dma_request);
793
794int s3c2410_dma_free(enum dma_ch id, struct s3c2410_dma_client *client)
795{
796 struct s3c_pl330_chan *ch;
797 struct s3c_pl330_xfer *xfer;
798 unsigned long flags;
799 int ret = 0;
800 unsigned idx;
801
802 spin_lock_irqsave(&res_lock, flags);
803
804 ch = id_to_chan(id);
805
806 if (!ch || chan_free(ch))
807 goto free_exit;
808
809 /* Refuse if someone else wanted to free the channel */
810 if (ch->client != client) {
811 ret = -EBUSY;
812 goto free_exit;
813 }
814
815 /* Stop any active xfer, Flushe the queue and do callbacks */
816 pl330_chan_ctrl(ch->pl330_chan_id, PL330_OP_FLUSH);
817
818 /* Abort the submitted requests */
819 idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
820
821 if (ch->req[idx].x) {
822 xfer = container_of(ch->req[idx].x,
823 struct s3c_pl330_xfer, px);
824
825 ch->req[idx].x = NULL;
826 del_from_queue(xfer);
827
828 spin_unlock_irqrestore(&res_lock, flags);
829 _finish_off(xfer, S3C2410_RES_ABORT, 1);
830 spin_lock_irqsave(&res_lock, flags);
831 }
832
833 if (ch->req[1 - idx].x) {
834 xfer = container_of(ch->req[1 - idx].x,
835 struct s3c_pl330_xfer, px);
836
837 ch->req[1 - idx].x = NULL;
838 del_from_queue(xfer);
839
840 spin_unlock_irqrestore(&res_lock, flags);
841 _finish_off(xfer, S3C2410_RES_ABORT, 1);
842 spin_lock_irqsave(&res_lock, flags);
843 }
844
845 /* Pluck and Abort the queued requests in order */
846 do {
847 xfer = get_from_queue(ch, 1);
848
849 spin_unlock_irqrestore(&res_lock, flags);
850 _finish_off(xfer, S3C2410_RES_ABORT, 1);
851 spin_lock_irqsave(&res_lock, flags);
852 } while (xfer);
853
854 ch->client = NULL;
855
856 pl330_release_channel(ch->pl330_chan_id);
857
858 ch->pl330_chan_id = NULL;
859
860 chan_release(ch);
861
862free_exit:
863 spin_unlock_irqrestore(&res_lock, flags);
864
865 return ret;
866}
867EXPORT_SYMBOL(s3c2410_dma_free);
868
869int s3c2410_dma_config(enum dma_ch id, int xferunit)
870{
871 struct s3c_pl330_chan *ch;
872 struct pl330_info *pi;
873 unsigned long flags;
874 int i, dbwidth, ret = 0;
875
876 spin_lock_irqsave(&res_lock, flags);
877
878 ch = id_to_chan(id);
879
880 if (!ch || chan_free(ch)) {
881 ret = -EINVAL;
882 goto cfg_exit;
883 }
884
885 pi = ch->dmac->pi;
886 dbwidth = pi->pcfg.data_bus_width / 8;
887
888 /* Max size of xfer can be pcfg.data_bus_width */
889 if (xferunit > dbwidth) {
890 ret = -EINVAL;
891 goto cfg_exit;
892 }
893
894 i = 0;
895 while (xferunit != (1 << i))
896 i++;
897
898 /* If valid value */
899 if (xferunit == (1 << i))
900 ch->rqcfg.brst_size = i;
901 else
902 ret = -EINVAL;
903
904cfg_exit:
905 spin_unlock_irqrestore(&res_lock, flags);
906
907 return ret;
908}
909EXPORT_SYMBOL(s3c2410_dma_config);
910
911/* Options that are supported by this driver */
912#define S3C_PL330_FLAGS (S3C2410_DMAF_CIRCULAR | S3C2410_DMAF_AUTOSTART)
913
914int s3c2410_dma_setflags(enum dma_ch id, unsigned int options)
915{
916 struct s3c_pl330_chan *ch;
917 unsigned long flags;
918 int ret = 0;
919
920 spin_lock_irqsave(&res_lock, flags);
921
922 ch = id_to_chan(id);
923
924 if (!ch || chan_free(ch) || options & ~(S3C_PL330_FLAGS))
925 ret = -EINVAL;
926 else
927 ch->options = options;
928
929 spin_unlock_irqrestore(&res_lock, flags);
930
931 return 0;
932}
933EXPORT_SYMBOL(s3c2410_dma_setflags);
934
935int s3c2410_dma_set_buffdone_fn(enum dma_ch id, s3c2410_dma_cbfn_t rtn)
936{
937 struct s3c_pl330_chan *ch;
938 unsigned long flags;
939 int ret = 0;
940
941 spin_lock_irqsave(&res_lock, flags);
942
943 ch = id_to_chan(id);
944
945 if (!ch || chan_free(ch))
946 ret = -EINVAL;
947 else
948 ch->callback_fn = rtn;
949
950 spin_unlock_irqrestore(&res_lock, flags);
951
952 return ret;
953}
954EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);
955
956int s3c2410_dma_devconfig(enum dma_ch id, enum s3c2410_dmasrc source,
957 unsigned long address)
958{
959 struct s3c_pl330_chan *ch;
960 unsigned long flags;
961 int ret = 0;
962
963 spin_lock_irqsave(&res_lock, flags);
964
965 ch = id_to_chan(id);
966
967 if (!ch || chan_free(ch)) {
968 ret = -EINVAL;
969 goto devcfg_exit;
970 }
971
972 switch (source) {
973 case S3C2410_DMASRC_HW: /* P->M */
974 ch->req[0].rqtype = DEVTOMEM;
975 ch->req[1].rqtype = DEVTOMEM;
976 ch->rqcfg.src_inc = 0;
977 ch->rqcfg.dst_inc = 1;
978 break;
979 case S3C2410_DMASRC_MEM: /* M->P */
980 ch->req[0].rqtype = MEMTODEV;
981 ch->req[1].rqtype = MEMTODEV;
982 ch->rqcfg.src_inc = 1;
983 ch->rqcfg.dst_inc = 0;
984 break;
985 default:
986 ret = -EINVAL;
987 goto devcfg_exit;
988 }
989
990 ch->sdaddr = address;
991
992devcfg_exit:
993 spin_unlock_irqrestore(&res_lock, flags);
994
995 return ret;
996}
997EXPORT_SYMBOL(s3c2410_dma_devconfig);
998
999int s3c2410_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
1000{
1001 struct s3c_pl330_chan *ch = id_to_chan(id);
1002 struct pl330_chanstatus status;
1003 int ret;
1004
1005 if (!ch || chan_free(ch))
1006 return -EINVAL;
1007
1008 ret = pl330_chan_status(ch->pl330_chan_id, &status);
1009 if (ret < 0)
1010 return ret;
1011
1012 *src = status.src_addr;
1013 *dst = status.dst_addr;
1014
1015 return 0;
1016}
1017EXPORT_SYMBOL(s3c2410_dma_getposition);
1018
1019static irqreturn_t pl330_irq_handler(int irq, void *data)
1020{
1021 if (pl330_update(data))
1022 return IRQ_HANDLED;
1023 else
1024 return IRQ_NONE;
1025}
1026
1027static int pl330_probe(struct platform_device *pdev)
1028{
1029 struct s3c_pl330_dmac *s3c_pl330_dmac;
1030 struct s3c_pl330_platdata *pl330pd;
1031 struct pl330_info *pl330_info;
1032 struct resource *res;
1033 int i, ret, irq;
1034
1035 pl330pd = pdev->dev.platform_data;
1036
1037 /* Can't do without the list of _32_ peripherals */
1038 if (!pl330pd || !pl330pd->peri) {
1039 dev_err(&pdev->dev, "platform data missing!\n");
1040 return -ENODEV;
1041 }
1042
1043 pl330_info = kzalloc(sizeof(*pl330_info), GFP_KERNEL);
1044 if (!pl330_info)
1045 return -ENOMEM;
1046
1047 pl330_info->pl330_data = NULL;
1048 pl330_info->dev = &pdev->dev;
1049
1050 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1051 if (!res) {
1052 ret = -ENODEV;
1053 goto probe_err1;
1054 }
1055
1056 request_mem_region(res->start, resource_size(res), pdev->name);
1057
1058 pl330_info->base = ioremap(res->start, resource_size(res));
1059 if (!pl330_info->base) {
1060 ret = -ENXIO;
1061 goto probe_err2;
1062 }
1063
1064 irq = platform_get_irq(pdev, 0);
1065 if (irq < 0) {
1066 ret = irq;
1067 goto probe_err3;
1068 }
1069
1070 ret = request_irq(irq, pl330_irq_handler, 0,
1071 dev_name(&pdev->dev), pl330_info);
1072 if (ret)
1073 goto probe_err4;
1074
1075 ret = pl330_add(pl330_info);
1076 if (ret)
1077 goto probe_err5;
1078
1079 /* Allocate a new DMAC */
1080 s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL);
1081 if (!s3c_pl330_dmac) {
1082 ret = -ENOMEM;
1083 goto probe_err6;
1084 }
1085
1086 /* Hook the info */
1087 s3c_pl330_dmac->pi = pl330_info;
1088
1089 /* No busy channels */
1090 s3c_pl330_dmac->busy_chan = 0;
1091
1092 s3c_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
1093 sizeof(struct s3c_pl330_xfer), 0, 0, NULL);
1094
1095 if (!s3c_pl330_dmac->kmcache) {
1096 ret = -ENOMEM;
1097 goto probe_err7;
1098 }
1099
1100 /* Get the list of peripherals */
1101 s3c_pl330_dmac->peri = pl330pd->peri;
1102
1103 /* Attach to the list of DMACs */
1104 list_add_tail(&s3c_pl330_dmac->node, &dmac_list);
1105
1106 /* Create a channel for each peripheral in the DMAC
1107 * that is, if it doesn't already exist
1108 */
1109 for (i = 0; i < PL330_MAX_PERI; i++)
1110 if (s3c_pl330_dmac->peri[i] != DMACH_MAX)
1111 chan_add(s3c_pl330_dmac->peri[i]);
1112
1113 printk(KERN_INFO
1114 "Loaded driver for PL330 DMAC-%d %s\n", pdev->id, pdev->name);
1115 printk(KERN_INFO
1116 "\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n",
1117 pl330_info->pcfg.data_buf_dep,
1118 pl330_info->pcfg.data_bus_width / 8, pl330_info->pcfg.num_chan,
1119 pl330_info->pcfg.num_peri, pl330_info->pcfg.num_events);
1120
1121 return 0;
1122
1123probe_err7:
1124 kfree(s3c_pl330_dmac);
1125probe_err6:
1126 pl330_del(pl330_info);
1127probe_err5:
1128 free_irq(irq, pl330_info);
1129probe_err4:
1130probe_err3:
1131 iounmap(pl330_info->base);
1132probe_err2:
1133 release_mem_region(res->start, resource_size(res));
1134probe_err1:
1135 kfree(pl330_info);
1136
1137 return ret;
1138}
1139
1140static int pl330_remove(struct platform_device *pdev)
1141{
1142 struct s3c_pl330_dmac *dmac, *d;
1143 struct s3c_pl330_chan *ch;
1144 unsigned long flags;
1145 int del, found;
1146
1147 if (!pdev->dev.platform_data)
1148 return -EINVAL;
1149
1150 spin_lock_irqsave(&res_lock, flags);
1151
1152 found = 0;
1153 list_for_each_entry(d, &dmac_list, node)
1154 if (d->pi->dev == &pdev->dev) {
1155 found = 1;
1156 break;
1157 }
1158
1159 if (!found) {
1160 spin_unlock_irqrestore(&res_lock, flags);
1161 return 0;
1162 }
1163
1164 dmac = d;
1165
1166 /* Remove all Channels that are managed only by this DMAC */
1167 list_for_each_entry(ch, &chan_list, node) {
1168
1169 /* Only channels that are handled by this DMAC */
1170 if (iface_of_dmac(dmac, ch->id))
1171 del = 1;
1172 else
1173 continue;
1174
1175 /* Don't remove if some other DMAC has it too */
1176 list_for_each_entry(d, &dmac_list, node)
1177 if (d != dmac && iface_of_dmac(d, ch->id)) {
1178 del = 0;
1179 break;
1180 }
1181
1182 if (del) {
1183 spin_unlock_irqrestore(&res_lock, flags);
1184 s3c2410_dma_free(ch->id, ch->client);
1185 spin_lock_irqsave(&res_lock, flags);
1186 list_del(&ch->node);
1187 kfree(ch);
1188 }
1189 }
1190
1191 /* Remove the DMAC */
1192 list_del(&dmac->node);
1193 kfree(dmac);
1194
1195 spin_unlock_irqrestore(&res_lock, flags);
1196
1197 return 0;
1198}
1199
1200static struct platform_driver pl330_driver = {
1201 .driver = {
1202 .owner = THIS_MODULE,
1203 .name = "s3c-pl330",
1204 },
1205 .probe = pl330_probe,
1206 .remove = pl330_remove,
1207};
1208
1209static int __init pl330_init(void)
1210{
1211 return platform_driver_register(&pl330_driver);
1212}
1213module_init(pl330_init);
1214
1215static void __exit pl330_exit(void)
1216{
1217 platform_driver_unregister(&pl330_driver);
1218 return;
1219}
1220module_exit(pl330_exit);
1221
1222MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
1223MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
1224MODULE_LICENSE("GPL");