staging: comedi: pcl818: fix pcl818_handle_dma() for short DMA transaction
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / pcl818.c
index ac19e83ce62a208335bf4520fa74ec07ae6fd89c..19f6b595a80e8086efaf58b87a5fe11fd325b7ea 100644 (file)
@@ -302,19 +302,21 @@ static const struct pcl818_board boardtypes[] = {
        },
 };
 
+struct pcl818_dma_desc {
+       void *virt_addr;        /* virtual address of DMA buffer */
+       dma_addr_t hw_addr;     /* hardware (bus) address of DMA buffer */
+       unsigned int size;      /* transfer size (in bytes) */
+};
+
 struct pcl818_private {
        unsigned int dma;       /*  used DMA, 0=don't use DMA */
-       unsigned int dmapages;
        unsigned int hwdmasize;
-       unsigned long dmabuf[2];        /*  pointers to begin of DMA buffers */
-       unsigned int hwdmaptr[2];       /*  hardware address of DMA buffers */
-       int next_dma_buf;       /*  which DMA buffer will be used next round */
+       struct pcl818_dma_desc dma_desc[2];
+       int cur_dma;
        long dma_runs_to_end;   /*  how many we must permorm DMA transfer to end of record */
        unsigned long last_dma_run;     /*  how many bytes we must transfer on last DMA page */
        unsigned int ns_min;    /*  manimal allowed delay between samples (in us) for actual card */
        int i8253_osc_base;     /*  1/frequency of on board oscilator in ns */
-       int ai_act_scan;        /*  how many scans we finished */
-       int ai_act_chan;        /*  actual position in actual scan */
        unsigned int act_chanlist[16];  /*  MUX setting for actual AI operations */
        unsigned int act_chanlist_len;  /*  how long is actual MUX list */
        unsigned int act_chanlist_pos;  /*  actual position in MUX list */
@@ -345,27 +347,28 @@ static void pcl818_ai_setup_dma(struct comedi_device *dev,
                                struct comedi_subdevice *s)
 {
        struct pcl818_private *devpriv = dev->private;
+       struct pcl818_dma_desc *dma = &devpriv->dma_desc[0];
        struct comedi_cmd *cmd = &s->async->cmd;
        unsigned int flags;
-       unsigned int bytes;
 
        disable_dma(devpriv->dma);      /*  disable dma */
-       bytes = devpriv->hwdmasize;
        if (cmd->stop_src == TRIG_COUNT) {
-               bytes = cmd->stop_arg * cfc_bytes_per_scan(s);
-               devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize;
-               devpriv->last_dma_run = bytes % devpriv->hwdmasize;
+               dma->size = cmd->stop_arg * comedi_bytes_per_scan(s);
+               devpriv->dma_runs_to_end = dma->size / devpriv->hwdmasize;
+               devpriv->last_dma_run = dma->size % devpriv->hwdmasize;
                devpriv->dma_runs_to_end--;
                if (devpriv->dma_runs_to_end >= 0)
-                       bytes = devpriv->hwdmasize;
+                       dma->size = devpriv->hwdmasize;
+       } else {
+               dma->size = devpriv->hwdmasize;
        }
 
-       devpriv->next_dma_buf = 0;
+       devpriv->cur_dma = 0;
        set_dma_mode(devpriv->dma, DMA_MODE_READ);
        flags = claim_dma_lock();
        clear_dma_ff(devpriv->dma);
-       set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]);
-       set_dma_count(devpriv->dma, bytes);
+       set_dma_addr(devpriv->dma, dma->hw_addr);
+       set_dma_count(devpriv->dma, dma->size);
        release_dma_lock(flags);
        enable_dma(devpriv->dma);
 }
@@ -375,20 +378,22 @@ static void pcl818_ai_setup_next_dma(struct comedi_device *dev,
 {
        struct pcl818_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
+       struct pcl818_dma_desc *dma;
        unsigned long flags;
 
        disable_dma(devpriv->dma);
-       devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
+       devpriv->cur_dma = 1 - devpriv->cur_dma;
        if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) {
                /* switch dma bufs */
-               set_dma_mode(devpriv->dma, DMA_MODE_READ);
-               flags = claim_dma_lock();
-               set_dma_addr(devpriv->dma,
-                            devpriv->hwdmaptr[devpriv->next_dma_buf]);
+               dma = &devpriv->dma_desc[devpriv->cur_dma];
                if (devpriv->dma_runs_to_end || cmd->stop_src == TRIG_NONE)
-                       set_dma_count(devpriv->dma, devpriv->hwdmasize);
+                       dma->size = devpriv->hwdmasize;
                else
-                       set_dma_count(devpriv->dma, devpriv->last_dma_run);
+                       dma->size = devpriv->last_dma_run;
+               set_dma_mode(devpriv->dma, DMA_MODE_READ);
+               flags = claim_dma_lock();
+               set_dma_addr(devpriv->dma, dma->hw_addr);
+               set_dma_count(devpriv->dma, dma->size);
                release_dma_lock(flags);
                enable_dma(devpriv->dma);
        }
@@ -521,21 +526,12 @@ static bool pcl818_ai_next_chan(struct comedi_device *dev,
        struct pcl818_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
 
-       s->async->events |= COMEDI_CB_BLOCK;
-
        devpriv->act_chanlist_pos++;
        if (devpriv->act_chanlist_pos >= devpriv->act_chanlist_len)
                devpriv->act_chanlist_pos = 0;
 
-       s->async->cur_chan++;
-       if (s->async->cur_chan >= cmd->chanlist_len) {
-               s->async->cur_chan = 0;
-               devpriv->ai_act_scan--;
-               s->async->events |= COMEDI_CB_EOS;
-       }
-
-       if (cmd->stop_src == TRIG_COUNT && devpriv->ai_act_scan == 0) {
-               /* all data sampled */
+       if (cmd->stop_src == TRIG_COUNT &&
+           s->async->scans_done >= cmd->stop_arg) {
                s->async->events |= COMEDI_CB_EOA;
                return false;
        }
@@ -560,7 +556,7 @@ static void pcl818_handle_eoc(struct comedi_device *dev,
        if (pcl818_ai_dropout(dev, s, chan))
                return;
 
-       comedi_buf_put(s, val);
+       comedi_buf_write_samples(s, &val, 1);
 
        pcl818_ai_next_chan(dev, s);
 }
@@ -569,27 +565,24 @@ static void pcl818_handle_dma(struct comedi_device *dev,
                              struct comedi_subdevice *s)
 {
        struct pcl818_private *devpriv = dev->private;
-       unsigned short *ptr;
+       struct pcl818_dma_desc *dma = &devpriv->dma_desc[devpriv->cur_dma];
+       unsigned short *ptr = dma->virt_addr;
+       unsigned int nsamples = comedi_bytes_to_samples(s, dma->size);
        unsigned int chan;
        unsigned int val;
-       int i, len, bufptr;
+       int i;
 
        pcl818_ai_setup_next_dma(dev, s);
 
-       ptr = (unsigned short *)devpriv->dmabuf[1 - devpriv->next_dma_buf];
-
-       len = devpriv->hwdmasize >> 1;
-       bufptr = 0;
-
-       for (i = 0; i < len; i++) {
-               val = ptr[bufptr++];
+       for (i = 0; i < nsamples; i++) {
+               val = ptr[i];
                chan = val & 0xf;
                val = (val >> 4) & s->maxdata;
 
                if (pcl818_ai_dropout(dev, s, chan))
                        break;
 
-               comedi_buf_put(s, val);
+               comedi_buf_write_samples(s, &val, 1);
 
                if (!pcl818_ai_next_chan(dev, s))
                        break;
@@ -630,7 +623,7 @@ static void pcl818_handle_fifo(struct comedi_device *dev,
                if (pcl818_ai_dropout(dev, s, chan))
                        break;
 
-               comedi_buf_put(s, val);
+               comedi_buf_write_samples(s, &val, 1);
 
                if (!pcl818_ai_next_chan(dev, s))
                        break;
@@ -642,6 +635,7 @@ static irqreturn_t pcl818_interrupt(int irq, void *d)
        struct comedi_device *dev = d;
        struct pcl818_private *devpriv = dev->private;
        struct comedi_subdevice *s = dev->read_subdev;
+       struct comedi_cmd *cmd = &s->async->cmd;
 
        if (!dev->attached || !devpriv->ai_cmd_running) {
                pcl818_ai_clear_eoc(dev);
@@ -655,7 +649,7 @@ static irqreturn_t pcl818_interrupt(int irq, void *d)
                 * being reprogrammed while a DMA transfer is in
                 * progress.
                 */
-               devpriv->ai_act_scan = 0;
+               s->async->scans_done = cmd->stop_arg;
                s->cancel(dev, s);
                return IRQ_HANDLED;
        }
@@ -669,7 +663,7 @@ static irqreturn_t pcl818_interrupt(int irq, void *d)
 
        pcl818_ai_clear_eoc(dev);
 
-       cfc_handle_events(dev, s);
+       comedi_handle_events(dev, s);
        return IRQ_HANDLED;
 }
 
@@ -829,8 +823,6 @@ static int pcl818_ai_cmd(struct comedi_device *dev,
        pcl818_ai_setup_chanlist(dev, cmd->chanlist, seglen);
 
        devpriv->ai_data_len = s->async->prealloc_bufsz;
-       devpriv->ai_act_scan = cmd->stop_arg;
-       devpriv->ai_act_chan = 0;
        devpriv->ai_cmd_running = 1;
        devpriv->ai_cmd_canceled = 0;
        devpriv->act_chanlist_pos = 0;
@@ -873,7 +865,8 @@ static int pcl818_ai_cancel(struct comedi_device *dev,
 
        if (devpriv->dma) {
                if (cmd->stop_src == TRIG_NONE ||
-                   (cmd->stop_src == TRIG_COUNT && devpriv->ai_act_scan > 0)) {
+                   (cmd->stop_src == TRIG_COUNT &&
+                    s->async->scans_done < cmd->stop_arg)) {
                        if (!devpriv->ai_cmd_canceled) {
                                /*
                                * Wait for running dma transfer to end,
@@ -1065,13 +1058,57 @@ static void pcl818_set_ai_range_table(struct comedi_device *dev,
        }
 }
 
+static int pcl818_alloc_dma(struct comedi_device *dev, unsigned int dma_chan)
+{
+       struct pcl818_private *devpriv = dev->private;
+       struct pcl818_dma_desc *dma;
+       int i;
+
+       if (!(dma_chan == 3 || dma_chan == 1))
+               return 0;
+
+       if (request_dma(dma_chan, dev->board_name))
+               return 0;
+       devpriv->dma = dma_chan;
+
+       devpriv->hwdmasize = PAGE_SIZE * 4;     /* we need 16KB */
+
+       for (i = 0; i < 2; i++) {
+               dma = &devpriv->dma_desc[i];
+
+               dma->virt_addr = dma_alloc_coherent(NULL, devpriv->hwdmasize,
+                                                   &dma->hw_addr, GFP_KERNEL);
+               if (!dma->virt_addr)
+                       return -ENOMEM;
+       }
+       return 0;
+}
+
+static void pcl818_free_dma(struct comedi_device *dev)
+{
+       struct pcl818_private *devpriv = dev->private;
+       struct pcl818_dma_desc *dma;
+       int i;
+
+       if (!devpriv)
+               return;
+
+       if (devpriv->dma)
+               free_dma(devpriv->dma);
+       for (i = 0; i < 2; i++) {
+               dma = &devpriv->dma_desc[i];
+               if (dma->virt_addr)
+                       dma_free_coherent(NULL, devpriv->hwdmasize,
+                                         dma->virt_addr, dma->hw_addr);
+       }
+}
+
 static int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 {
        const struct pcl818_board *board = dev->board_ptr;
        struct pcl818_private *devpriv;
        struct comedi_subdevice *s;
        int ret;
-       int i;
 
        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
        if (!devpriv)
@@ -1095,30 +1132,10 @@ static int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it)
                devpriv->usefifo = 1;
 
        /* we need an IRQ to do DMA on channel 3 or 1 */
-       if (dev->irq && board->has_dma &&
-           (it->options[2] == 3 || it->options[2] == 1)) {
-               ret = request_dma(it->options[2], dev->board_name);
-               if (ret) {
-                       dev_err(dev->class_dev,
-                               "unable to request DMA channel %d\n",
-                               it->options[2]);
-                       return -EBUSY;
-               }
-               devpriv->dma = it->options[2];
-
-               devpriv->dmapages = 2;  /* we need 16KB */
-               devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE;
-
-               for (i = 0; i < 2; i++) {
-                       unsigned long dmabuf;
-
-                       dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages);
-                       if (!dmabuf)
-                               return -ENOMEM;
-
-                       devpriv->dmabuf[i] = dmabuf;
-                       devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf);
-               }
+       if (dev->irq && board->has_dma) {
+               ret = pcl818_alloc_dma(dev, it->options[2]);
+               if (ret)
+                       return ret;
        }
 
        ret = comedi_alloc_subdevices(dev, 4);
@@ -1169,7 +1186,6 @@ static int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it)
                                s->range_table = &range_unknown;
                }
                s->insn_write   = pcl818_ao_insn_write;
-               s->insn_read    = comedi_readback_insn_read;
 
                ret = comedi_alloc_subdev_readback(s);
                if (ret)
@@ -1222,13 +1238,8 @@ static void pcl818_detach(struct comedi_device *dev)
        if (devpriv) {
                pcl818_ai_cancel(dev, dev->read_subdev);
                pcl818_reset(dev);
-               if (devpriv->dma)
-                       free_dma(devpriv->dma);
-               if (devpriv->dmabuf[0])
-                       free_pages(devpriv->dmabuf[0], devpriv->dmapages);
-               if (devpriv->dmabuf[1])
-                       free_pages(devpriv->dmabuf[1], devpriv->dmapages);
        }
+       pcl818_free_dma(dev);
        comedi_legacy_detach(dev);
 }