Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * | |
3 | * BRIEF MODULE DESCRIPTION | |
4 | * A DMA channel allocator for Au1000. API is modeled loosely off of | |
5 | * linux/kernel/dma.c. | |
6 | * | |
7 | * Copyright 2000 MontaVista Software Inc. | |
8 | * Author: MontaVista Software, Inc. | |
9 | * stevel@mvista.com or source@mvista.com | |
10 | * Copyright (C) 2005 Ralf Baechle (ralf@linux-mips.org) | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or modify it | |
13 | * under the terms of the GNU General Public License as published by the | |
14 | * Free Software Foundation; either version 2 of the License, or (at your | |
15 | * option) any later version. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN | |
20 | * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |
23 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
24 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
27 | * | |
28 | * You should have received a copy of the GNU General Public License along | |
29 | * with this program; if not, write to the Free Software Foundation, Inc., | |
30 | * 675 Mass Ave, Cambridge, MA 02139, USA. | |
31 | * | |
32 | */ | |
1da177e4 LT |
33 | #include <linux/module.h> |
34 | #include <linux/kernel.h> | |
35 | #include <linux/errno.h> | |
36 | #include <linux/sched.h> | |
37 | #include <linux/spinlock.h> | |
38 | #include <linux/string.h> | |
39 | #include <linux/delay.h> | |
40 | #include <linux/interrupt.h> | |
1da177e4 LT |
41 | #include <asm/system.h> |
42 | #include <asm/mach-au1x00/au1000.h> | |
43 | #include <asm/mach-au1x00/au1000_dma.h> | |
44 | ||
45 | #if defined(CONFIG_SOC_AU1000) || defined(CONFIG_SOC_AU1500) || defined(CONFIG_SOC_AU1100) | |
46 | /* | |
47 | * A note on resource allocation: | |
48 | * | |
49 | * All drivers needing DMA channels, should allocate and release them | |
50 | * through the public routines `request_dma()' and `free_dma()'. | |
51 | * | |
52 | * In order to avoid problems, all processes should allocate resources in | |
53 | * the same sequence and release them in the reverse order. | |
54 | * | |
55 | * So, when allocating DMAs and IRQs, first allocate the DMA, then the IRQ. | |
56 | * When releasing them, first release the IRQ, then release the DMA. The | |
57 | * main reason for this order is that, if you are requesting the DMA buffer | |
58 | * done interrupt, you won't know the irq number until the DMA channel is | |
59 | * returned from request_dma. | |
60 | */ | |
61 | ||
62 | ||
63 | DEFINE_SPINLOCK(au1000_dma_spin_lock); | |
64 | ||
65 | struct dma_chan au1000_dma_table[NUM_AU1000_DMA_CHANNELS] = { | |
66 | {.dev_id = -1,}, | |
67 | {.dev_id = -1,}, | |
68 | {.dev_id = -1,}, | |
69 | {.dev_id = -1,}, | |
70 | {.dev_id = -1,}, | |
71 | {.dev_id = -1,}, | |
72 | {.dev_id = -1,}, | |
73 | {.dev_id = -1,} | |
74 | }; | |
75 | EXPORT_SYMBOL(au1000_dma_table); | |
76 | ||
77 | // Device FIFO addresses and default DMA modes | |
78 | static const struct dma_dev { | |
79 | unsigned int fifo_addr; | |
80 | unsigned int dma_mode; | |
81 | } dma_dev_table[DMA_NUM_DEV] = { | |
82 | {UART0_ADDR + UART_TX, 0}, | |
83 | {UART0_ADDR + UART_RX, 0}, | |
84 | {0, 0}, | |
85 | {0, 0}, | |
86 | {AC97C_DATA, DMA_DW16 }, // coherent | |
87 | {AC97C_DATA, DMA_DR | DMA_DW16 }, // coherent | |
88 | {UART3_ADDR + UART_TX, DMA_DW8 | DMA_NC}, | |
89 | {UART3_ADDR + UART_RX, DMA_DR | DMA_DW8 | DMA_NC}, | |
90 | {USBD_EP0RD, DMA_DR | DMA_DW8 | DMA_NC}, | |
91 | {USBD_EP0WR, DMA_DW8 | DMA_NC}, | |
92 | {USBD_EP2WR, DMA_DW8 | DMA_NC}, | |
93 | {USBD_EP3WR, DMA_DW8 | DMA_NC}, | |
94 | {USBD_EP4RD, DMA_DR | DMA_DW8 | DMA_NC}, | |
95 | {USBD_EP5RD, DMA_DR | DMA_DW8 | DMA_NC}, | |
96 | {I2S_DATA, DMA_DW32 | DMA_NC}, | |
97 | {I2S_DATA, DMA_DR | DMA_DW32 | DMA_NC} | |
98 | }; | |
99 | ||
100 | int au1000_dma_read_proc(char *buf, char **start, off_t fpos, | |
101 | int length, int *eof, void *data) | |
102 | { | |
103 | int i, len = 0; | |
104 | struct dma_chan *chan; | |
105 | ||
106 | for (i = 0; i < NUM_AU1000_DMA_CHANNELS; i++) { | |
107 | if ((chan = get_dma_chan(i)) != NULL) { | |
108 | len += sprintf(buf + len, "%2d: %s\n", | |
109 | i, chan->dev_str); | |
110 | } | |
111 | } | |
112 | ||
113 | if (fpos >= len) { | |
114 | *start = buf; | |
115 | *eof = 1; | |
116 | return 0; | |
117 | } | |
118 | *start = buf + fpos; | |
119 | if ((len -= fpos) > length) | |
120 | return length; | |
121 | *eof = 1; | |
122 | return len; | |
123 | } | |
124 | ||
125 | // Device FIFO addresses and default DMA modes - 2nd bank | |
126 | static const struct dma_dev dma_dev_table_bank2[DMA_NUM_DEV_BANK2] = { | |
127 | {SD0_XMIT_FIFO, DMA_DS | DMA_DW8}, // coherent | |
128 | {SD0_RECV_FIFO, DMA_DS | DMA_DR | DMA_DW8}, // coherent | |
129 | {SD1_XMIT_FIFO, DMA_DS | DMA_DW8}, // coherent | |
130 | {SD1_RECV_FIFO, DMA_DS | DMA_DR | DMA_DW8} // coherent | |
131 | }; | |
132 | ||
133 | void dump_au1000_dma_channel(unsigned int dmanr) | |
134 | { | |
135 | struct dma_chan *chan; | |
136 | ||
137 | if (dmanr >= NUM_AU1000_DMA_CHANNELS) | |
138 | return; | |
139 | chan = &au1000_dma_table[dmanr]; | |
140 | ||
141 | printk(KERN_INFO "Au1000 DMA%d Register Dump:\n", dmanr); | |
142 | printk(KERN_INFO " mode = 0x%08x\n", | |
143 | au_readl(chan->io + DMA_MODE_SET)); | |
144 | printk(KERN_INFO " addr = 0x%08x\n", | |
145 | au_readl(chan->io + DMA_PERIPHERAL_ADDR)); | |
146 | printk(KERN_INFO " start0 = 0x%08x\n", | |
147 | au_readl(chan->io + DMA_BUFFER0_START)); | |
148 | printk(KERN_INFO " start1 = 0x%08x\n", | |
149 | au_readl(chan->io + DMA_BUFFER1_START)); | |
150 | printk(KERN_INFO " count0 = 0x%08x\n", | |
151 | au_readl(chan->io + DMA_BUFFER0_COUNT)); | |
152 | printk(KERN_INFO " count1 = 0x%08x\n", | |
153 | au_readl(chan->io + DMA_BUFFER1_COUNT)); | |
154 | } | |
155 | ||
156 | ||
157 | /* | |
158 | * Finds a free channel, and binds the requested device to it. | |
159 | * Returns the allocated channel number, or negative on error. | |
160 | * Requests the DMA done IRQ if irqhandler != NULL. | |
161 | */ | |
162 | int request_au1000_dma(int dev_id, const char *dev_str, | |
937a8015 | 163 | irqreturn_t (*irqhandler)(int, void *), |
1da177e4 LT |
164 | unsigned long irqflags, |
165 | void *irq_dev_id) | |
166 | { | |
167 | struct dma_chan *chan; | |
168 | const struct dma_dev *dev; | |
169 | int i, ret; | |
170 | ||
171 | #if defined(CONFIG_SOC_AU1100) | |
172 | if (dev_id < 0 || dev_id >= (DMA_NUM_DEV + DMA_NUM_DEV_BANK2)) | |
173 | return -EINVAL; | |
174 | #else | |
175 | if (dev_id < 0 || dev_id >= DMA_NUM_DEV) | |
a3dddd56 | 176 | return -EINVAL; |
1da177e4 LT |
177 | #endif |
178 | ||
179 | for (i = 0; i < NUM_AU1000_DMA_CHANNELS; i++) { | |
180 | if (au1000_dma_table[i].dev_id < 0) | |
181 | break; | |
182 | } | |
183 | if (i == NUM_AU1000_DMA_CHANNELS) | |
184 | return -ENODEV; | |
185 | ||
186 | chan = &au1000_dma_table[i]; | |
187 | ||
188 | if (dev_id >= DMA_NUM_DEV) { | |
189 | dev_id -= DMA_NUM_DEV; | |
190 | dev = &dma_dev_table_bank2[dev_id]; | |
191 | } else { | |
192 | dev = &dma_dev_table[dev_id]; | |
193 | } | |
194 | ||
195 | if (irqhandler) { | |
196 | chan->irq = AU1000_DMA_INT_BASE + i; | |
197 | chan->irq_dev = irq_dev_id; | |
198 | if ((ret = request_irq(chan->irq, irqhandler, irqflags, | |
199 | dev_str, chan->irq_dev))) { | |
200 | chan->irq = 0; | |
201 | chan->irq_dev = NULL; | |
202 | return ret; | |
203 | } | |
204 | } else { | |
205 | chan->irq = 0; | |
206 | chan->irq_dev = NULL; | |
207 | } | |
208 | ||
209 | // fill it in | |
210 | chan->io = DMA_CHANNEL_BASE + i * DMA_CHANNEL_LEN; | |
211 | chan->dev_id = dev_id; | |
212 | chan->dev_str = dev_str; | |
213 | chan->fifo_addr = dev->fifo_addr; | |
214 | chan->mode = dev->dma_mode; | |
215 | ||
216 | /* initialize the channel before returning */ | |
217 | init_dma(i); | |
218 | ||
219 | return i; | |
220 | } | |
221 | EXPORT_SYMBOL(request_au1000_dma); | |
222 | ||
223 | void free_au1000_dma(unsigned int dmanr) | |
224 | { | |
225 | struct dma_chan *chan = get_dma_chan(dmanr); | |
226 | if (!chan) { | |
227 | printk("Trying to free DMA%d\n", dmanr); | |
228 | return; | |
229 | } | |
230 | ||
231 | disable_dma(dmanr); | |
232 | if (chan->irq) | |
233 | free_irq(chan->irq, chan->irq_dev); | |
234 | ||
235 | chan->irq = 0; | |
236 | chan->irq_dev = NULL; | |
237 | chan->dev_id = -1; | |
238 | } | |
239 | EXPORT_SYMBOL(free_au1000_dma); | |
240 | ||
241 | #endif // AU1000 AU1500 AU1100 |