]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * arch/sh/drivers/dma/dma-g2.c | |
3 | * | |
4 | * G2 bus DMA support | |
5 | * | |
e2d1864d | 6 | * Copyright (C) 2003 - 2006 Paul Mundt |
1da177e4 LT |
7 | * |
8 | * This file is subject to the terms and conditions of the GNU General Public | |
9 | * License. See the file "COPYING" in the main directory of this archive | |
10 | * for more details. | |
11 | */ | |
12 | #include <linux/init.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/interrupt.h> | |
e2d1864d | 16 | #include <asm/cacheflush.h> |
1da177e4 LT |
17 | #include <asm/mach/sysasic.h> |
18 | #include <asm/mach/dma.h> | |
19 | #include <asm/dma.h> | |
20 | ||
21 | struct g2_channel { | |
22 | unsigned long g2_addr; /* G2 bus address */ | |
23 | unsigned long root_addr; /* Root bus (SH-4) address */ | |
24 | unsigned long size; /* Size (in bytes), 32-byte aligned */ | |
25 | unsigned long direction; /* Transfer direction */ | |
26 | unsigned long ctrl; /* Transfer control */ | |
27 | unsigned long chan_enable; /* Channel enable */ | |
28 | unsigned long xfer_enable; /* Transfer enable */ | |
29 | unsigned long xfer_stat; /* Transfer status */ | |
30 | } __attribute__ ((aligned(32))); | |
31 | ||
32 | struct g2_status { | |
33 | unsigned long g2_addr; | |
34 | unsigned long root_addr; | |
35 | unsigned long size; | |
36 | unsigned long status; | |
37 | } __attribute__ ((aligned(16))); | |
38 | ||
39 | struct g2_dma_info { | |
40 | struct g2_channel channel[G2_NR_DMA_CHANNELS]; | |
41 | unsigned long pad1[G2_NR_DMA_CHANNELS]; | |
42 | unsigned long wait_state; | |
43 | unsigned long pad2[10]; | |
44 | unsigned long magic; | |
45 | struct g2_status status[G2_NR_DMA_CHANNELS]; | |
46 | } __attribute__ ((aligned(256))); | |
47 | ||
48 | static volatile struct g2_dma_info *g2_dma = (volatile struct g2_dma_info *)0xa05f7800; | |
49 | ||
e2d1864d PM |
50 | #define g2_bytes_remaining(i) \ |
51 | ((g2_dma->channel[i].size - \ | |
52 | g2_dma->status[i].size) & 0x0fffffff) | |
53 | ||
1da177e4 LT |
54 | static irqreturn_t g2_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs) |
55 | { | |
e2d1864d | 56 | int i; |
1da177e4 | 57 | |
e2d1864d PM |
58 | for (i = 0; i < G2_NR_DMA_CHANNELS; i++) { |
59 | if (g2_dma->status[i].status & 0x20000000) { | |
60 | unsigned int bytes = g2_bytes_remaining(i); | |
61 | ||
62 | if (likely(bytes == 0)) { | |
63 | struct dma_info *info = dev_id; | |
64 | struct dma_channel *chan = info->channels + i; | |
65 | ||
66 | wake_up(&chan->wait_queue); | |
67 | ||
68 | return IRQ_HANDLED; | |
69 | } | |
70 | } | |
71 | } | |
72 | ||
73 | return IRQ_NONE; | |
74 | } | |
1da177e4 LT |
75 | |
76 | static int g2_enable_dma(struct dma_channel *chan) | |
77 | { | |
78 | unsigned int chan_nr = chan->chan; | |
79 | ||
80 | g2_dma->channel[chan_nr].chan_enable = 1; | |
81 | g2_dma->channel[chan_nr].xfer_enable = 1; | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
86 | static int g2_disable_dma(struct dma_channel *chan) | |
87 | { | |
88 | unsigned int chan_nr = chan->chan; | |
89 | ||
90 | g2_dma->channel[chan_nr].chan_enable = 0; | |
91 | g2_dma->channel[chan_nr].xfer_enable = 0; | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | static int g2_xfer_dma(struct dma_channel *chan) | |
97 | { | |
98 | unsigned int chan_nr = chan->chan; | |
99 | ||
100 | if (chan->sar & 31) { | |
101 | printk("g2dma: unaligned source 0x%lx\n", chan->sar); | |
102 | return -EINVAL; | |
103 | } | |
104 | ||
105 | if (chan->dar & 31) { | |
106 | printk("g2dma: unaligned dest 0x%lx\n", chan->dar); | |
107 | return -EINVAL; | |
108 | } | |
109 | ||
110 | /* Align the count */ | |
111 | if (chan->count & 31) | |
112 | chan->count = (chan->count + (32 - 1)) & ~(32 - 1); | |
113 | ||
114 | /* Fixup destination */ | |
115 | chan->dar += 0xa0800000; | |
116 | ||
117 | /* Fixup direction */ | |
118 | chan->mode = !chan->mode; | |
119 | ||
120 | flush_icache_range((unsigned long)chan->sar, chan->count); | |
121 | ||
122 | g2_disable_dma(chan); | |
123 | ||
124 | g2_dma->channel[chan_nr].g2_addr = chan->dar & 0x1fffffe0; | |
125 | g2_dma->channel[chan_nr].root_addr = chan->sar & 0x1fffffe0; | |
126 | g2_dma->channel[chan_nr].size = (chan->count & ~31) | 0x80000000; | |
127 | g2_dma->channel[chan_nr].direction = chan->mode; | |
128 | ||
129 | /* | |
130 | * bit 0 - ??? | |
131 | * bit 1 - if set, generate a hardware event on transfer completion | |
132 | * bit 2 - ??? something to do with suspend? | |
133 | */ | |
134 | g2_dma->channel[chan_nr].ctrl = 5; /* ?? */ | |
135 | ||
136 | g2_enable_dma(chan); | |
137 | ||
138 | /* debug cruft */ | |
139 | pr_debug("count, sar, dar, mode, ctrl, chan, xfer: %ld, 0x%08lx, " | |
140 | "0x%08lx, %ld, %ld, %ld, %ld\n", | |
141 | g2_dma->channel[chan_nr].size, | |
142 | g2_dma->channel[chan_nr].root_addr, | |
143 | g2_dma->channel[chan_nr].g2_addr, | |
144 | g2_dma->channel[chan_nr].direction, | |
145 | g2_dma->channel[chan_nr].ctrl, | |
146 | g2_dma->channel[chan_nr].chan_enable, | |
147 | g2_dma->channel[chan_nr].xfer_enable); | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
e2d1864d PM |
152 | static int g2_get_residue(struct dma_channel *chan) |
153 | { | |
154 | return g2_bytes_remaining(chan->chan); | |
155 | } | |
156 | ||
1da177e4 LT |
157 | static struct dma_ops g2_dma_ops = { |
158 | .xfer = g2_xfer_dma, | |
e2d1864d | 159 | .get_residue = g2_get_residue, |
1da177e4 LT |
160 | }; |
161 | ||
162 | static struct dma_info g2_dma_info = { | |
0d831770 | 163 | .name = "g2_dmac", |
1da177e4 LT |
164 | .nr_channels = 4, |
165 | .ops = &g2_dma_ops, | |
166 | .flags = DMAC_CHANNELS_TEI_CAPABLE, | |
167 | }; | |
168 | ||
169 | static int __init g2_dma_init(void) | |
170 | { | |
e2d1864d PM |
171 | int ret; |
172 | ||
173 | ret = request_irq(HW_EVENT_G2_DMA, g2_dma_interrupt, IRQF_DISABLED, | |
174 | "g2 DMA handler", &g2_dma_info); | |
175 | if (unlikely(ret)) | |
176 | return -EINVAL; | |
1da177e4 LT |
177 | |
178 | /* Magic */ | |
179 | g2_dma->wait_state = 27; | |
180 | g2_dma->magic = 0x4659404f; | |
181 | ||
e2d1864d PM |
182 | ret = register_dmac(&g2_dma_info); |
183 | if (unlikely(ret != 0)) | |
184 | free_irq(HW_EVENT_G2_DMA, 0); | |
185 | ||
186 | return ret; | |
1da177e4 LT |
187 | } |
188 | ||
189 | static void __exit g2_dma_exit(void) | |
190 | { | |
191 | free_irq(HW_EVENT_G2_DMA, 0); | |
0d831770 | 192 | unregister_dmac(&g2_dma_info); |
1da177e4 LT |
193 | } |
194 | ||
195 | subsys_initcall(g2_dma_init); | |
196 | module_exit(g2_dma_exit); | |
197 | ||
198 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); | |
199 | MODULE_DESCRIPTION("G2 bus DMA driver"); | |
200 | MODULE_LICENSE("GPL"); |