| 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(usec: 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(sizeof(*dma), GFP_KERNEL); |
| 165 | if (!dma) |
| 166 | goto no_dma; |
| 167 | |
| 168 | desc = kcalloc(n_desc, 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 | |