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