| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * COMEDI ISA DMA support functions |
| * Copyright (c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/dma-mapping.h> |
| #include <asm/dma.h> |
| |
| #include "../comedidev.h" |
| |
| #include "comedi_isadma.h" |
| |
| /** |
| * comedi_isadma_program - program and enable an ISA DMA transfer |
| * @desc: the ISA DMA cookie to program and enable |
| */ |
| void comedi_isadma_program(struct comedi_isadma_desc *desc) |
| { |
| unsigned long flags; |
| |
| flags = claim_dma_lock(); |
| clear_dma_ff(desc->chan); |
| set_dma_mode(desc->chan, desc->mode); |
| set_dma_addr(desc->chan, desc->hw_addr); |
| set_dma_count(desc->chan, desc->size); |
| enable_dma(desc->chan); |
| release_dma_lock(flags); |
| } |
| EXPORT_SYMBOL_GPL(comedi_isadma_program); |
| |
| /** |
| * comedi_isadma_disable - disable the ISA DMA channel |
| * @dma_chan: the DMA channel to disable |
| * |
| * Returns the residue (remaining bytes) left in the DMA transfer. |
| */ |
| unsigned int comedi_isadma_disable(unsigned int dma_chan) |
| { |
| unsigned long flags; |
| unsigned int residue; |
| |
| flags = claim_dma_lock(); |
| disable_dma(dma_chan); |
| residue = get_dma_residue(dma_chan); |
| release_dma_lock(flags); |
| |
| return residue; |
| } |
| EXPORT_SYMBOL_GPL(comedi_isadma_disable); |
| |
| /** |
| * comedi_isadma_disable_on_sample - disable the ISA DMA channel |
| * @dma_chan: the DMA channel to disable |
| * @size: the sample size (in bytes) |
| * |
| * Returns the residue (remaining bytes) left in the DMA transfer. |
| */ |
| unsigned int comedi_isadma_disable_on_sample(unsigned int dma_chan, |
| unsigned int size) |
| { |
| int stalled = 0; |
| unsigned long flags; |
| unsigned int residue; |
| unsigned int new_residue; |
| |
| residue = comedi_isadma_disable(dma_chan); |
| while (residue % size) { |
| /* residue is a partial sample, enable DMA to allow more data */ |
| flags = claim_dma_lock(); |
| enable_dma(dma_chan); |
| release_dma_lock(flags); |
| |
| udelay(2); |
| new_residue = comedi_isadma_disable(dma_chan); |
| |
| /* is DMA stalled? */ |
| if (new_residue == residue) { |
| stalled++; |
| if (stalled > 10) |
| break; |
| } else { |
| residue = new_residue; |
| stalled = 0; |
| } |
| } |
| return residue; |
| } |
| EXPORT_SYMBOL_GPL(comedi_isadma_disable_on_sample); |
| |
| /** |
| * comedi_isadma_poll - poll the current DMA transfer |
| * @dma: the ISA DMA to poll |
| * |
| * Returns the position (in bytes) of the current DMA transfer. |
| */ |
| unsigned int comedi_isadma_poll(struct comedi_isadma *dma) |
| { |
| struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; |
| unsigned long flags; |
| unsigned int result; |
| unsigned int result1; |
| |
| flags = claim_dma_lock(); |
| clear_dma_ff(desc->chan); |
| if (!isa_dma_bridge_buggy) |
| disable_dma(desc->chan); |
| result = get_dma_residue(desc->chan); |
| /* |
| * Read the counter again and choose higher value in order to |
| * avoid reading during counter lower byte roll over if the |
| * isa_dma_bridge_buggy is set. |
| */ |
| result1 = get_dma_residue(desc->chan); |
| if (!isa_dma_bridge_buggy) |
| enable_dma(desc->chan); |
| release_dma_lock(flags); |
| |
| if (result < result1) |
| result = result1; |
| if (result >= desc->size || result == 0) |
| return 0; |
| return desc->size - result; |
| } |
| EXPORT_SYMBOL_GPL(comedi_isadma_poll); |
| |
| /** |
| * comedi_isadma_set_mode - set the ISA DMA transfer direction |
| * @desc: the ISA DMA cookie to set |
| * @dma_dir: the DMA direction |
| */ |
| void comedi_isadma_set_mode(struct comedi_isadma_desc *desc, char dma_dir) |
| { |
| desc->mode = (dma_dir == COMEDI_ISADMA_READ) ? DMA_MODE_READ |
| : DMA_MODE_WRITE; |
| } |
| EXPORT_SYMBOL_GPL(comedi_isadma_set_mode); |
| |
| /** |
| * comedi_isadma_alloc - allocate and initialize the ISA DMA |
| * @dev: comedi_device struct |
| * @n_desc: the number of cookies to allocate |
| * @dma_chan1: DMA channel for the first cookie |
| * @dma_chan2: DMA channel for the second cookie |
| * @maxsize: the size of the buffer to allocate for each cookie |
| * @dma_dir: the DMA direction |
| * |
| * Returns the allocated and initialized ISA DMA or NULL if anything fails. |
| */ |
| struct comedi_isadma *comedi_isadma_alloc(struct comedi_device *dev, |
| int n_desc, unsigned int dma_chan1, |
| unsigned int dma_chan2, |
| unsigned int maxsize, char dma_dir) |
| { |
| struct comedi_isadma *dma = NULL; |
| struct comedi_isadma_desc *desc; |
| unsigned int dma_chans[2]; |
| int i; |
| |
| if (n_desc < 1 || n_desc > 2) |
| goto no_dma; |
| |
| dma = kzalloc(sizeof(*dma), GFP_KERNEL); |
| if (!dma) |
| goto no_dma; |
| |
| desc = kcalloc(n_desc, sizeof(*desc), GFP_KERNEL); |
| if (!desc) |
| goto no_dma; |
| dma->desc = desc; |
| dma->n_desc = n_desc; |
| if (dev->hw_dev) { |
| dma->dev = dev->hw_dev; |
| } else { |
| /* Fall back to using the "class" device. */ |
| if (!dev->class_dev) |
| goto no_dma; |
| /* Need 24-bit mask for ISA DMA. */ |
| if (dma_coerce_mask_and_coherent(dev->class_dev, |
| DMA_BIT_MASK(24))) { |
| goto no_dma; |
| } |
| dma->dev = dev->class_dev; |
| } |
| |
| dma_chans[0] = dma_chan1; |
| if (dma_chan2 == 0 || dma_chan2 == dma_chan1) |
| dma_chans[1] = dma_chan1; |
| else |
| dma_chans[1] = dma_chan2; |
| |
| if (request_dma(dma_chans[0], dev->board_name)) |
| goto no_dma; |
| dma->chan = dma_chans[0]; |
| if (dma_chans[1] != dma_chans[0]) { |
| if (request_dma(dma_chans[1], dev->board_name)) |
| goto no_dma; |
| } |
| dma->chan2 = dma_chans[1]; |
| |
| for (i = 0; i < n_desc; i++) { |
| desc = &dma->desc[i]; |
| desc->chan = dma_chans[i]; |
| desc->maxsize = maxsize; |
| desc->virt_addr = dma_alloc_coherent(dma->dev, desc->maxsize, |
| &desc->hw_addr, |
| GFP_KERNEL); |
| if (!desc->virt_addr) |
| goto no_dma; |
| comedi_isadma_set_mode(desc, dma_dir); |
| } |
| |
| return dma; |
| |
| no_dma: |
| comedi_isadma_free(dma); |
| return NULL; |
| } |
| EXPORT_SYMBOL_GPL(comedi_isadma_alloc); |
| |
| /** |
| * comedi_isadma_free - free the ISA DMA |
| * @dma: the ISA DMA to free |
| */ |
| void comedi_isadma_free(struct comedi_isadma *dma) |
| { |
| struct comedi_isadma_desc *desc; |
| int i; |
| |
| if (!dma) |
| return; |
| |
| if (dma->desc) { |
| for (i = 0; i < dma->n_desc; i++) { |
| desc = &dma->desc[i]; |
| if (desc->virt_addr) |
| dma_free_coherent(dma->dev, desc->maxsize, |
| desc->virt_addr, |
| desc->hw_addr); |
| } |
| kfree(dma->desc); |
| } |
| if (dma->chan2 && dma->chan2 != dma->chan) |
| free_dma(dma->chan2); |
| if (dma->chan) |
| free_dma(dma->chan); |
| kfree(dma); |
| } |
| EXPORT_SYMBOL_GPL(comedi_isadma_free); |
| |
| static int __init comedi_isadma_init(void) |
| { |
| return 0; |
| } |
| module_init(comedi_isadma_init); |
| |
| static void __exit comedi_isadma_exit(void) |
| { |
| } |
| module_exit(comedi_isadma_exit); |
| |
| MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); |
| MODULE_DESCRIPTION("Comedi ISA DMA support"); |
| MODULE_LICENSE("GPL"); |