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