Commit | Line | Data |
---|---|---|
20a1ea7d HS |
1 | /* |
2 | * COMEDI ISA DMA support functions | |
3 | * Copyright (c) 2014 H Hartley Sweeten <hsweeten@visionengravers.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 | ||
16 | #include <linux/module.h> | |
17 | #include <linux/slab.h> | |
18 | #include <linux/delay.h> | |
19 | #include <linux/dma-mapping.h> | |
20 | #include <asm/dma.h> | |
21 | ||
22 | #include "../comedidev.h" | |
23 | ||
24 | #include "comedi_isadma.h" | |
25 | ||
26 | /** | |
27 | * comedi_isadma_program - program and enable an ISA DMA transfer | |
28 | * @desc: the ISA DMA cookie to program and enable | |
29 | */ | |
30 | void comedi_isadma_program(struct comedi_isadma_desc *desc) | |
31 | { | |
32 | unsigned long flags; | |
33 | ||
34 | flags = claim_dma_lock(); | |
35 | clear_dma_ff(desc->chan); | |
36 | set_dma_mode(desc->chan, desc->mode); | |
37 | set_dma_addr(desc->chan, desc->hw_addr); | |
38 | set_dma_count(desc->chan, desc->size); | |
39 | enable_dma(desc->chan); | |
40 | release_dma_lock(flags); | |
41 | } | |
42 | EXPORT_SYMBOL_GPL(comedi_isadma_program); | |
43 | ||
44 | /** | |
45 | * comedi_isadma_disable - disable the ISA DMA channel | |
46 | * @dma_chan: the DMA channel to disable | |
47 | * | |
48 | * Returns the residue (remaining bytes) left in the DMA transfer. | |
49 | */ | |
50 | unsigned int comedi_isadma_disable(unsigned int dma_chan) | |
51 | { | |
52 | unsigned long flags; | |
53 | unsigned int residue; | |
54 | ||
55 | flags = claim_dma_lock(); | |
56 | disable_dma(dma_chan); | |
57 | residue = get_dma_residue(dma_chan); | |
58 | release_dma_lock(flags); | |
59 | ||
60 | return residue; | |
61 | } | |
62 | EXPORT_SYMBOL_GPL(comedi_isadma_disable); | |
63 | ||
64 | /** | |
65 | * comedi_isadma_disable_on_sample - disable the ISA DMA channel | |
66 | * @dma_chan: the DMA channel to disable | |
67 | * @size: the sample size (in bytes) | |
68 | * | |
69 | * Returns the residue (remaining bytes) left in the DMA transfer. | |
70 | */ | |
71 | unsigned int comedi_isadma_disable_on_sample(unsigned int dma_chan, | |
72 | unsigned int size) | |
73 | { | |
74 | int stalled = 0; | |
75 | unsigned long flags; | |
76 | unsigned int residue; | |
77 | unsigned int new_residue; | |
78 | ||
79 | residue = comedi_isadma_disable(dma_chan); | |
80 | while (residue % size) { | |
81 | /* residue is a partial sample, enable DMA to allow more data */ | |
82 | flags = claim_dma_lock(); | |
83 | enable_dma(dma_chan); | |
84 | release_dma_lock(flags); | |
85 | ||
86 | udelay(2); | |
87 | new_residue = comedi_isadma_disable(dma_chan); | |
88 | ||
89 | /* is DMA stalled? */ | |
90 | if (new_residue == residue) { | |
91 | stalled++; | |
92 | if (stalled > 10) | |
93 | break; | |
7e4f1e77 HS |
94 | } else { |
95 | residue = new_residue; | |
96 | stalled = 0; | |
20a1ea7d | 97 | } |
20a1ea7d HS |
98 | } |
99 | return residue; | |
100 | } | |
101 | EXPORT_SYMBOL_GPL(comedi_isadma_disable_on_sample); | |
102 | ||
103 | /** | |
104 | * comedi_isadma_poll - poll the current DMA transfer | |
105 | * @dma: the ISA DMA to poll | |
106 | * | |
107 | * Returns the position (in bytes) of the current DMA transfer. | |
108 | */ | |
109 | unsigned int comedi_isadma_poll(struct comedi_isadma *dma) | |
110 | { | |
111 | struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; | |
112 | unsigned long flags; | |
113 | unsigned int result; | |
114 | unsigned int result1; | |
115 | ||
116 | flags = claim_dma_lock(); | |
117 | clear_dma_ff(desc->chan); | |
118 | if (!isa_dma_bridge_buggy) | |
119 | disable_dma(desc->chan); | |
120 | result = get_dma_residue(desc->chan); | |
121 | /* | |
122 | * Read the counter again and choose higher value in order to | |
123 | * avoid reading during counter lower byte roll over if the | |
124 | * isa_dma_bridge_buggy is set. | |
125 | */ | |
126 | result1 = get_dma_residue(desc->chan); | |
127 | if (!isa_dma_bridge_buggy) | |
128 | enable_dma(desc->chan); | |
129 | release_dma_lock(flags); | |
130 | ||
131 | if (result < result1) | |
132 | result = result1; | |
133 | if (result >= desc->size || result == 0) | |
134 | return 0; | |
135 | else | |
136 | return desc->size - result; | |
137 | } | |
138 | EXPORT_SYMBOL_GPL(comedi_isadma_poll); | |
139 | ||
140 | /** | |
141 | * comedi_isadma_set_mode - set the ISA DMA transfer direction | |
142 | * @desc: the ISA DMA cookie to set | |
143 | * @dma_dir: the DMA direction | |
144 | */ | |
145 | void comedi_isadma_set_mode(struct comedi_isadma_desc *desc, char dma_dir) | |
146 | { | |
147 | desc->mode = (dma_dir == COMEDI_ISADMA_READ) ? DMA_MODE_READ | |
148 | : DMA_MODE_WRITE; | |
149 | } | |
150 | EXPORT_SYMBOL_GPL(comedi_isadma_set_mode); | |
151 | ||
152 | /** | |
153 | * comedi_isadma_alloc - allocate and initialize the ISA DMA | |
154 | * @dev: comedi_device struct | |
155 | * @n_desc: the number of cookies to allocate | |
156 | * @dma_chan: DMA channel for the first cookie | |
157 | * @dma_chan2: DMA channel for the second cookie | |
158 | * @maxsize: the size of the buffer to allocate for each cookie | |
159 | * @dma_dir: the DMA direction | |
160 | * | |
161 | * Returns the allocated and initialized ISA DMA or NULL if anything fails. | |
162 | */ | |
163 | struct comedi_isadma *comedi_isadma_alloc(struct comedi_device *dev, | |
164 | int n_desc, unsigned int dma_chan1, | |
165 | unsigned int dma_chan2, | |
166 | unsigned int maxsize, char dma_dir) | |
167 | { | |
168 | struct comedi_isadma *dma = NULL; | |
169 | struct comedi_isadma_desc *desc; | |
170 | unsigned int dma_chans[2]; | |
171 | int i; | |
172 | ||
173 | if (n_desc < 1 || n_desc > 2) | |
174 | goto no_dma; | |
175 | ||
176 | dma = kzalloc(sizeof(*dma), GFP_KERNEL); | |
177 | if (!dma) | |
178 | goto no_dma; | |
179 | ||
180 | desc = kcalloc(n_desc, sizeof(*desc), GFP_KERNEL); | |
181 | if (!desc) | |
182 | goto no_dma; | |
183 | dma->desc = desc; | |
184 | dma->n_desc = n_desc; | |
185 | ||
186 | dma_chans[0] = dma_chan1; | |
187 | if (dma_chan2 == 0 || dma_chan2 == dma_chan1) | |
188 | dma_chans[1] = dma_chan1; | |
189 | else | |
190 | dma_chans[1] = dma_chan2; | |
191 | ||
192 | if (request_dma(dma_chans[0], dev->board_name)) | |
193 | goto no_dma; | |
194 | dma->chan = dma_chans[0]; | |
195 | if (dma_chans[1] != dma_chans[0]) { | |
196 | if (request_dma(dma_chans[1], dev->board_name)) | |
197 | goto no_dma; | |
198 | } | |
199 | dma->chan2 = dma_chans[1]; | |
200 | ||
201 | for (i = 0; i < n_desc; i++) { | |
202 | desc = &dma->desc[i]; | |
203 | desc->chan = dma_chans[i]; | |
204 | desc->maxsize = maxsize; | |
205 | desc->virt_addr = dma_alloc_coherent(NULL, desc->maxsize, | |
206 | &desc->hw_addr, | |
207 | GFP_KERNEL); | |
208 | if (!desc->virt_addr) | |
209 | goto no_dma; | |
210 | comedi_isadma_set_mode(desc, dma_dir); | |
211 | } | |
212 | ||
213 | return dma; | |
214 | ||
215 | no_dma: | |
216 | comedi_isadma_free(dma); | |
217 | return NULL; | |
218 | } | |
219 | EXPORT_SYMBOL_GPL(comedi_isadma_alloc); | |
220 | ||
221 | /** | |
222 | * comedi_isadma_free - free the ISA DMA | |
223 | * @dma: the ISA DMA to free | |
224 | */ | |
225 | void comedi_isadma_free(struct comedi_isadma *dma) | |
226 | { | |
227 | struct comedi_isadma_desc *desc; | |
228 | int i; | |
229 | ||
230 | if (!dma) | |
231 | return; | |
232 | ||
233 | if (dma->desc) { | |
234 | for (i = 0; i < dma->n_desc; i++) { | |
235 | desc = &dma->desc[i]; | |
236 | if (desc->virt_addr) | |
237 | dma_free_coherent(NULL, desc->maxsize, | |
6c7d2c8b HS |
238 | desc->virt_addr, |
239 | desc->hw_addr); | |
20a1ea7d HS |
240 | } |
241 | kfree(dma->desc); | |
242 | } | |
243 | if (dma->chan2 && dma->chan2 != dma->chan) | |
244 | free_dma(dma->chan2); | |
245 | if (dma->chan) | |
246 | free_dma(dma->chan); | |
247 | kfree(dma); | |
248 | } | |
249 | EXPORT_SYMBOL_GPL(comedi_isadma_free); | |
250 | ||
251 | static int __init comedi_isadma_init(void) | |
252 | { | |
253 | return 0; | |
254 | } | |
255 | module_init(comedi_isadma_init); | |
256 | ||
257 | static void __exit comedi_isadma_exit(void) | |
258 | { | |
259 | } | |
260 | module_exit(comedi_isadma_exit); | |
261 | ||
262 | MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); | |
263 | MODULE_DESCRIPTION("Comedi ISA DMA support"); | |
264 | MODULE_LICENSE("GPL"); |