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
 176        dma_chans[0] = dma_chan1;
 177        if (dma_chan2 == 0 || dma_chan2 == dma_chan1)
 178                dma_chans[1] = dma_chan1;
 179        else
 180                dma_chans[1] = dma_chan2;
 181
 182        if (request_dma(dma_chans[0], dev->board_name))
 183                goto no_dma;
 184        dma->chan = dma_chans[0];
 185        if (dma_chans[1] != dma_chans[0]) {
 186                if (request_dma(dma_chans[1], dev->board_name))
 187                        goto no_dma;
 188        }
 189        dma->chan2 = dma_chans[1];
 190
 191        for (i = 0; i < n_desc; i++) {
 192                desc = &dma->desc[i];
 193                desc->chan = dma_chans[i];
 194                desc->maxsize = maxsize;
 195                desc->virt_addr = dma_alloc_coherent(NULL, desc->maxsize,
 196                                                     &desc->hw_addr,
 197                                                     GFP_KERNEL);
 198                if (!desc->virt_addr)
 199                        goto no_dma;
 200                comedi_isadma_set_mode(desc, dma_dir);
 201        }
 202
 203        return dma;
 204
 205no_dma:
 206        comedi_isadma_free(dma);
 207        return NULL;
 208}
 209EXPORT_SYMBOL_GPL(comedi_isadma_alloc);
 210
 211/**
 212 * comedi_isadma_free - free the ISA DMA
 213 * @dma:        the ISA DMA to free
 214 */
 215void comedi_isadma_free(struct comedi_isadma *dma)
 216{
 217        struct comedi_isadma_desc *desc;
 218        int i;
 219
 220        if (!dma)
 221                return;
 222
 223        if (dma->desc) {
 224                for (i = 0; i < dma->n_desc; i++) {
 225                        desc = &dma->desc[i];
 226                        if (desc->virt_addr)
 227                                dma_free_coherent(NULL, desc->maxsize,
 228                                                  desc->virt_addr,
 229                                                  desc->hw_addr);
 230                }
 231                kfree(dma->desc);
 232        }
 233        if (dma->chan2 && dma->chan2 != dma->chan)
 234                free_dma(dma->chan2);
 235        if (dma->chan)
 236                free_dma(dma->chan);
 237        kfree(dma);
 238}
 239EXPORT_SYMBOL_GPL(comedi_isadma_free);
 240
 241static int __init comedi_isadma_init(void)
 242{
 243        return 0;
 244}
 245module_init(comedi_isadma_init);
 246
 247static void __exit comedi_isadma_exit(void)
 248{
 249}
 250module_exit(comedi_isadma_exit);
 251
 252MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
 253MODULE_DESCRIPTION("Comedi ISA DMA support");
 254MODULE_LICENSE("GPL");
 255