1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * comedi/drivers/ni_labpc_isadma.c
4 * ISA DMA support for National Instruments Lab-PC series boards and
5 * compatibles.
6 *
7 * Extracted from ni_labpc.c:
8 * Copyright (C) 2001-2003 Frank Mori Hess <fmhess@users.sourceforge.net>
9 */
10
11#include <linux/module.h>
12#include <linux/slab.h>
13#include <linux/comedi/comedidev.h>
14#include <linux/comedi/comedi_isadma.h>
15
16#include "ni_labpc.h"
17#include "ni_labpc_regs.h"
18#include "ni_labpc_isadma.h"
19
20/* size in bytes of dma buffer */
21#define LABPC_ISADMA_BUFFER_SIZE	0xff00
22
23/* utility function that suggests a dma transfer size in bytes */
24static unsigned int labpc_suggest_transfer_size(struct comedi_device *dev,
25						struct comedi_subdevice *s,
26						unsigned int maxbytes)
27{
28	struct comedi_cmd *cmd = &s->async->cmd;
29	unsigned int sample_size = comedi_bytes_per_sample(s);
30	unsigned int size;
31	unsigned int freq;
32
33	if (cmd->convert_src == TRIG_TIMER)
34		freq = 1000000000 / cmd->convert_arg;
35	else
36		/* return some default value */
37		freq = 0xffffffff;
38
39	/* make buffer fill in no more than 1/3 second */
40	size = (freq / 3) * sample_size;
41
42	/* set a minimum and maximum size allowed */
43	if (size > maxbytes)
44		size = maxbytes;
45	else if (size < sample_size)
46		size = sample_size;
47
48	return size;
49}
50
51void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s)
52{
53	struct labpc_private *devpriv = dev->private;
54	struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
55	struct comedi_cmd *cmd = &s->async->cmd;
56	unsigned int sample_size = comedi_bytes_per_sample(s);
57
58	/* set appropriate size of transfer */
59	desc->size = labpc_suggest_transfer_size(dev, s, desc->maxsize);
60	if (cmd->stop_src == TRIG_COUNT &&
61	    devpriv->count * sample_size < desc->size)
62		desc->size = devpriv->count * sample_size;
63
64	comedi_isadma_program(desc);
65
66	/* set CMD3 bits for caller to enable DMA and interrupt */
67	devpriv->cmd3 |= (CMD3_DMAEN | CMD3_DMATCINTEN);
68}
69EXPORT_SYMBOL_GPL(labpc_setup_dma);
70
71void labpc_drain_dma(struct comedi_device *dev)
72{
73	struct labpc_private *devpriv = dev->private;
74	struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
75	struct comedi_subdevice *s = dev->read_subdev;
76	struct comedi_async *async = s->async;
77	struct comedi_cmd *cmd = &async->cmd;
78	unsigned int max_samples = comedi_bytes_to_samples(s, desc->size);
79	unsigned int residue;
80	unsigned int nsamples;
81	unsigned int leftover;
82
83	/*
84	 * residue is the number of bytes left to be done on the dma
85	 * transfer.  It should always be zero at this point unless
86	 * the stop_src is set to external triggering.
87	 */
88	residue = comedi_isadma_disable(desc->chan);
89
90	/*
91	 * Figure out how many samples to read for this transfer and
92	 * how many will be stored for next time.
93	 */
94	nsamples = max_samples - comedi_bytes_to_samples(s, residue);
95	if (cmd->stop_src == TRIG_COUNT) {
96		if (devpriv->count <= nsamples) {
97			nsamples = devpriv->count;
98			leftover = 0;
99		} else {
100			leftover = devpriv->count - nsamples;
101			if (leftover > max_samples)
102				leftover = max_samples;
103		}
104		devpriv->count -= nsamples;
105	} else {
106		leftover = max_samples;
107	}
108	desc->size = comedi_samples_to_bytes(s, leftover);
109
110	comedi_buf_write_samples(s, desc->virt_addr, nsamples);
111}
112EXPORT_SYMBOL_GPL(labpc_drain_dma);
113
114static void handle_isa_dma(struct comedi_device *dev)
115{
116	struct labpc_private *devpriv = dev->private;
117	struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
118
119	labpc_drain_dma(dev);
120
121	if (desc->size)
122		comedi_isadma_program(desc);
123
124	/* clear dma tc interrupt */
125	devpriv->write_byte(dev, 0x1, DMATC_CLEAR_REG);
126}
127
128void labpc_handle_dma_status(struct comedi_device *dev)
129{
130	const struct labpc_boardinfo *board = dev->board_ptr;
131	struct labpc_private *devpriv = dev->private;
132
133	/*
134	 * if a dma terminal count of external stop trigger
135	 * has occurred
136	 */
137	if (devpriv->stat1 & STAT1_GATA0 ||
138	    (board->is_labpc1200 && devpriv->stat2 & STAT2_OUTA1))
139		handle_isa_dma(dev);
140}
141EXPORT_SYMBOL_GPL(labpc_handle_dma_status);
142
143void labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan)
144{
145	struct labpc_private *devpriv = dev->private;
146
147	/* only DMA channels 3 and 1 are valid */
148	if (dma_chan != 1 && dma_chan != 3)
149		return;
150
151	/* DMA uses 1 buffer */
152	devpriv->dma = comedi_isadma_alloc(dev, 1, dma_chan, dma_chan,
153					   LABPC_ISADMA_BUFFER_SIZE,
154					   COMEDI_ISADMA_READ);
155}
156EXPORT_SYMBOL_GPL(labpc_init_dma_chan);
157
158void labpc_free_dma_chan(struct comedi_device *dev)
159{
160	struct labpc_private *devpriv = dev->private;
161
162	if (devpriv)
163		comedi_isadma_free(devpriv->dma);
164}
165EXPORT_SYMBOL_GPL(labpc_free_dma_chan);
166
167static int __init ni_labpc_isadma_init_module(void)
168{
169	return 0;
170}
171module_init(ni_labpc_isadma_init_module);
172
173static void __exit ni_labpc_isadma_cleanup_module(void)
174{
175}
176module_exit(ni_labpc_isadma_cleanup_module);
177
178MODULE_AUTHOR("Comedi https://www.comedi.org");
179MODULE_DESCRIPTION("Comedi NI Lab-PC ISA DMA support");
180MODULE_LICENSE("GPL");
181