// SPDX-License-Identifier: GPL-2.0+ /* * COMEDI ISA DMA support functions * Copyright (c) 2014 H Hartley Sweeten */ #include #include #include #include #include #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_chan: 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 "); MODULE_DESCRIPTION("Comedi ISA DMA support"); MODULE_LICENSE("GPL");