]>
Commit | Line | Data |
---|---|---|
e184e2be | 1 | // SPDX-License-Identifier: GPL-2.0+ |
6baef150 | 2 | /* |
a88a6376 HS |
3 | * pcmmio.c |
4 | * Driver for Winsystems PC-104 based multifunction IO board. | |
5 | * | |
6 | * COMEDI - Linux Control and Measurement Device Interface | |
7 | * Copyright (C) 2007 Calin A. Culianu <calin@ajvar.org> | |
a88a6376 | 8 | */ |
6baef150 | 9 | |
6baef150 | 10 | /* |
a88a6376 HS |
11 | * Driver: pcmmio |
12 | * Description: A driver for the PCM-MIO multifunction board | |
c07e5ede | 13 | * Devices: [Winsystems] PCM-MIO (pcmmio) |
a88a6376 HS |
14 | * Author: Calin Culianu <calin@ajvar.org> |
15 | * Updated: Wed, May 16 2007 16:21:10 -0500 | |
16 | * Status: works | |
17 | * | |
18 | * A driver for the PCM-MIO multifunction board from Winsystems. This | |
19 | * is a PC-104 based I/O board. It contains four subdevices: | |
20 | * | |
21 | * subdevice 0 - 16 channels of 16-bit AI | |
22 | * subdevice 1 - 8 channels of 16-bit AO | |
23 | * subdevice 2 - first 24 channels of the 48 channel of DIO | |
24 | * (with edge-triggered interrupt support) | |
25 | * subdevice 3 - last 24 channels of the 48 channel DIO | |
26 | * (no interrupt support for this bank of channels) | |
27 | * | |
28 | * Some notes: | |
29 | * | |
30 | * Synchronous reads and writes are the only things implemented for analog | |
31 | * input and output. The hardware itself can do streaming acquisition, etc. | |
32 | * | |
33 | * Asynchronous I/O for the DIO subdevices *is* implemented, however! They | |
34 | * are basically edge-triggered interrupts for any configuration of the | |
35 | * channels in subdevice 2. | |
36 | * | |
37 | * Also note that this interrupt support is untested. | |
38 | * | |
39 | * A few words about edge-detection IRQ support (commands on DIO): | |
40 | * | |
41 | * To use edge-detection IRQ support for the DIO subdevice, pass the IRQ | |
42 | * of the board to the comedi_config command. The board IRQ is not jumpered | |
43 | * but rather configured through software, so any IRQ from 1-15 is OK. | |
44 | * | |
45 | * Due to the genericity of the comedi API, you need to create a special | |
46 | * comedi_command in order to use edge-triggered interrupts for DIO. | |
47 | * | |
48 | * Use comedi_commands with TRIG_NOW. Your callback will be called each | |
49 | * time an edge is detected on the specified DIO line(s), and the data | |
50 | * values will be two sample_t's, which should be concatenated to form | |
51 | * one 32-bit unsigned int. This value is the mask of channels that had | |
52 | * edges detected from your channel list. Note that the bits positions | |
53 | * in the mask correspond to positions in your chanlist when you | |
54 | * specified the command and *not* channel id's! | |
55 | * | |
56 | * To set the polarity of the edge-detection interrupts pass a nonzero value | |
57 | * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero | |
58 | * value for both CR_RANGE and CR_AREF if you want edge-down polarity. | |
59 | * | |
60 | * Configuration Options: | |
61 | * [0] - I/O port base address | |
62 | * [1] - IRQ (optional -- for edge-detect interrupt support only, | |
63 | * leave out if you don't need this feature) | |
64 | */ | |
6baef150 | 65 | |
ce157f80 | 66 | #include <linux/module.h> |
25436dc9 | 67 | #include <linux/interrupt.h> |
5a0e3ad6 | 68 | #include <linux/slab.h> |
f95d45d1 | 69 | |
6baef150 | 70 | #include "../comedidev.h" |
33782dd5 | 71 | |
8b0fcb59 HS |
72 | /* |
73 | * Register I/O map | |
74 | */ | |
68533c7b HS |
75 | #define PCMMIO_AI_LSB_REG 0x00 |
76 | #define PCMMIO_AI_MSB_REG 0x01 | |
77 | #define PCMMIO_AI_CMD_REG 0x02 | |
16948306 RKM |
78 | #define PCMMIO_AI_CMD_SE BIT(7) |
79 | #define PCMMIO_AI_CMD_ODD_CHAN BIT(6) | |
68533c7b HS |
80 | #define PCMMIO_AI_CMD_CHAN_SEL(x) (((x) & 0x3) << 4) |
81 | #define PCMMIO_AI_CMD_RANGE(x) (((x) & 0x3) << 2) | |
2eb6b518 HS |
82 | #define PCMMIO_RESOURCE_REG 0x02 |
83 | #define PCMMIO_RESOURCE_IRQ(x) (((x) & 0xf) << 0) | |
68533c7b | 84 | #define PCMMIO_AI_STATUS_REG 0x03 |
16948306 RKM |
85 | #define PCMMIO_AI_STATUS_DATA_READY BIT(7) |
86 | #define PCMMIO_AI_STATUS_DATA_DMA_PEND BIT(6) | |
87 | #define PCMMIO_AI_STATUS_CMD_DMA_PEND BIT(5) | |
88 | #define PCMMIO_AI_STATUS_IRQ_PEND BIT(4) | |
89 | #define PCMMIO_AI_STATUS_DATA_DRQ_ENA BIT(2) | |
90 | #define PCMMIO_AI_STATUS_REG_SEL BIT(3) | |
91 | #define PCMMIO_AI_STATUS_CMD_DRQ_ENA BIT(1) | |
92 | #define PCMMIO_AI_STATUS_IRQ_ENA BIT(0) | |
2eb6b518 HS |
93 | #define PCMMIO_AI_RES_ENA_REG 0x03 |
94 | #define PCMMIO_AI_RES_ENA_CMD_REG_ACCESS (0 << 3) | |
16948306 RKM |
95 | #define PCMMIO_AI_RES_ENA_AI_RES_ACCESS BIT(3) |
96 | #define PCMMIO_AI_RES_ENA_DIO_RES_ACCESS BIT(4) | |
68533c7b HS |
97 | #define PCMMIO_AI_2ND_ADC_OFFSET 0x04 |
98 | ||
99 | #define PCMMIO_AO_LSB_REG 0x08 | |
100 | #define PCMMIO_AO_LSB_SPAN(x) (((x) & 0xf) << 0) | |
101 | #define PCMMIO_AO_MSB_REG 0x09 | |
102 | #define PCMMIO_AO_CMD_REG 0x0a | |
103 | #define PCMMIO_AO_CMD_WR_SPAN (0x2 << 4) | |
104 | #define PCMMIO_AO_CMD_WR_CODE (0x3 << 4) | |
105 | #define PCMMIO_AO_CMD_UPDATE (0x4 << 4) | |
106 | #define PCMMIO_AO_CMD_UPDATE_ALL (0x5 << 4) | |
107 | #define PCMMIO_AO_CMD_WR_SPAN_UPDATE (0x6 << 4) | |
108 | #define PCMMIO_AO_CMD_WR_CODE_UPDATE (0x7 << 4) | |
109 | #define PCMMIO_AO_CMD_WR_SPAN_UPDATE_ALL (0x8 << 4) | |
110 | #define PCMMIO_AO_CMD_WR_CODE_UPDATE_ALL (0x9 << 4) | |
111 | #define PCMMIO_AO_CMD_RD_B1_SPAN (0xa << 4) | |
112 | #define PCMMIO_AO_CMD_RD_B1_CODE (0xb << 4) | |
113 | #define PCMMIO_AO_CMD_RD_B2_SPAN (0xc << 4) | |
114 | #define PCMMIO_AO_CMD_RD_B2_CODE (0xd << 4) | |
115 | #define PCMMIO_AO_CMD_NOP (0xf << 4) | |
116 | #define PCMMIO_AO_CMD_CHAN_SEL(x) (((x) & 0x03) << 1) | |
117 | #define PCMMIO_AO_CMD_CHAN_SEL_ALL (0x0f << 0) | |
118 | #define PCMMIO_AO_STATUS_REG 0x0b | |
16948306 RKM |
119 | #define PCMMIO_AO_STATUS_DATA_READY BIT(7) |
120 | #define PCMMIO_AO_STATUS_DATA_DMA_PEND BIT(6) | |
121 | #define PCMMIO_AO_STATUS_CMD_DMA_PEND BIT(5) | |
122 | #define PCMMIO_AO_STATUS_IRQ_PEND BIT(4) | |
123 | #define PCMMIO_AO_STATUS_DATA_DRQ_ENA BIT(2) | |
124 | #define PCMMIO_AO_STATUS_REG_SEL BIT(3) | |
125 | #define PCMMIO_AO_STATUS_CMD_DRQ_ENA BIT(1) | |
126 | #define PCMMIO_AO_STATUS_IRQ_ENA BIT(0) | |
68533c7b HS |
127 | #define PCMMIO_AO_RESOURCE_ENA_REG 0x0b |
128 | #define PCMMIO_AO_2ND_DAC_OFFSET 0x04 | |
8b0fcb59 | 129 | |
03a36f17 HS |
130 | /* |
131 | * WinSystems WS16C48 | |
132 | * | |
133 | * Offset Page 0 Page 1 Page 2 Page 3 | |
134 | * ------ ----------- ----------- ----------- ----------- | |
135 | * 0x10 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O | |
136 | * 0x11 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O | |
137 | * 0x12 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O | |
138 | * 0x13 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O | |
139 | * 0x14 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O | |
140 | * 0x15 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O | |
141 | * 0x16 INT_PENDING INT_PENDING INT_PENDING INT_PENDING | |
142 | * 0x17 Page/Lock Page/Lock Page/Lock Page/Lock | |
143 | * 0x18 N/A POL_0 ENAB_0 INT_ID0 | |
144 | * 0x19 N/A POL_1 ENAB_1 INT_ID1 | |
145 | * 0x1a N/A POL_2 ENAB_2 INT_ID2 | |
146 | */ | |
147 | #define PCMMIO_PORT_REG(x) (0x10 + (x)) | |
148 | #define PCMMIO_INT_PENDING_REG 0x16 | |
149 | #define PCMMIO_PAGE_LOCK_REG 0x17 | |
150 | #define PCMMIO_LOCK_PORT(x) ((1 << (x)) & 0x3f) | |
151 | #define PCMMIO_PAGE(x) (((x) & 0x3) << 6) | |
152 | #define PCMMIO_PAGE_MASK PCMUIO_PAGE(3) | |
153 | #define PCMMIO_PAGE_POL 1 | |
154 | #define PCMMIO_PAGE_ENAB 2 | |
155 | #define PCMMIO_PAGE_INT_ID 3 | |
156 | #define PCMMIO_PAGE_REG(x) (0x18 + (x)) | |
157 | ||
f9ec4efd HS |
158 | static const struct comedi_lrange pcmmio_ai_ranges = { |
159 | 4, { | |
160 | BIP_RANGE(5), | |
161 | BIP_RANGE(10), | |
162 | UNI_RANGE(5), | |
163 | UNI_RANGE(10) | |
164 | } | |
6baef150 CC |
165 | }; |
166 | ||
f9ec4efd HS |
167 | static const struct comedi_lrange pcmmio_ao_ranges = { |
168 | 6, { | |
169 | UNI_RANGE(5), | |
170 | UNI_RANGE(10), | |
171 | BIP_RANGE(5), | |
172 | BIP_RANGE(10), | |
173 | BIP_RANGE(2.5), | |
174 | RANGE(-2.5, 7.5) | |
175 | } | |
6baef150 CC |
176 | }; |
177 | ||
e56ab715 | 178 | struct pcmmio_private { |
83d55bd0 | 179 | spinlock_t pagelock; /* protects the page registers */ |
35c5e884 | 180 | spinlock_t spinlock; /* protects the member variables */ |
23bafad0 | 181 | unsigned int enabled_mask; |
23bafad0 | 182 | unsigned int active:1; |
e56ab715 | 183 | }; |
6baef150 | 184 | |
4edac4a4 HS |
185 | static void pcmmio_dio_write(struct comedi_device *dev, unsigned int val, |
186 | int page, int port) | |
187 | { | |
188 | struct pcmmio_private *devpriv = dev->private; | |
189 | unsigned long iobase = dev->iobase; | |
190 | unsigned long flags; | |
191 | ||
192 | spin_lock_irqsave(&devpriv->pagelock, flags); | |
193 | if (page == 0) { | |
194 | /* Port registers are valid for any page */ | |
195 | outb(val & 0xff, iobase + PCMMIO_PORT_REG(port + 0)); | |
196 | outb((val >> 8) & 0xff, iobase + PCMMIO_PORT_REG(port + 1)); | |
197 | outb((val >> 16) & 0xff, iobase + PCMMIO_PORT_REG(port + 2)); | |
198 | } else { | |
199 | outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG); | |
200 | outb(val & 0xff, iobase + PCMMIO_PAGE_REG(0)); | |
201 | outb((val >> 8) & 0xff, iobase + PCMMIO_PAGE_REG(1)); | |
202 | outb((val >> 16) & 0xff, iobase + PCMMIO_PAGE_REG(2)); | |
203 | } | |
204 | spin_unlock_irqrestore(&devpriv->pagelock, flags); | |
205 | } | |
206 | ||
0398606c HS |
207 | static unsigned int pcmmio_dio_read(struct comedi_device *dev, |
208 | int page, int port) | |
209 | { | |
210 | struct pcmmio_private *devpriv = dev->private; | |
211 | unsigned long iobase = dev->iobase; | |
212 | unsigned long flags; | |
213 | unsigned int val; | |
214 | ||
215 | spin_lock_irqsave(&devpriv->pagelock, flags); | |
216 | if (page == 0) { | |
217 | /* Port registers are valid for any page */ | |
218 | val = inb(iobase + PCMMIO_PORT_REG(port + 0)); | |
219 | val |= (inb(iobase + PCMMIO_PORT_REG(port + 1)) << 8); | |
220 | val |= (inb(iobase + PCMMIO_PORT_REG(port + 2)) << 16); | |
221 | } else { | |
222 | outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG); | |
223 | val = inb(iobase + PCMMIO_PAGE_REG(0)); | |
224 | val |= (inb(iobase + PCMMIO_PAGE_REG(1)) << 8); | |
225 | val |= (inb(iobase + PCMMIO_PAGE_REG(2)) << 16); | |
226 | } | |
227 | spin_unlock_irqrestore(&devpriv->pagelock, flags); | |
228 | ||
229 | return val; | |
230 | } | |
231 | ||
232 | /* | |
233 | * Each channel can be individually programmed for input or output. | |
234 | * Writing a '0' to a channel causes the corresponding output pin | |
235 | * to go to a high-z state (pulled high by an external 10K resistor). | |
236 | * This allows it to be used as an input. When used in the input mode, | |
237 | * a read reflects the inverted state of the I/O pin, such that a | |
238 | * high on the pin will read as a '0' in the register. Writing a '1' | |
239 | * to a bit position causes the pin to sink current (up to 12mA), | |
240 | * effectively pulling it low. | |
241 | */ | |
0a85b6f0 MT |
242 | static int pcmmio_dio_insn_bits(struct comedi_device *dev, |
243 | struct comedi_subdevice *s, | |
0398606c HS |
244 | struct comedi_insn *insn, |
245 | unsigned int *data) | |
6baef150 | 246 | { |
0398606c HS |
247 | /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */ |
248 | int port = s->index == 2 ? 0 : 3; | |
249 | unsigned int chanmask = (1 << s->n_chan) - 1; | |
250 | unsigned int mask; | |
251 | unsigned int val; | |
252 | ||
253 | mask = comedi_dio_update_state(s, data); | |
254 | if (mask) { | |
255 | /* | |
256 | * Outputs are inverted, invert the state and | |
257 | * update the channels. | |
258 | * | |
259 | * The s->io_bits mask makes sure the input channels | |
260 | * are '0' so that the outputs pins stay in a high | |
261 | * z-state. | |
262 | */ | |
263 | val = ~s->state & chanmask; | |
264 | val &= s->io_bits; | |
265 | pcmmio_dio_write(dev, val, 0, port); | |
6baef150 CC |
266 | } |
267 | ||
0398606c HS |
268 | /* get inverted state of the channels from the port */ |
269 | val = pcmmio_dio_read(dev, 0, port); | |
270 | ||
271 | /* return the true state of the channels */ | |
272 | data[1] = ~val & chanmask; | |
6baef150 | 273 | |
a2714e3e | 274 | return insn->n; |
6baef150 CC |
275 | } |
276 | ||
0a85b6f0 MT |
277 | static int pcmmio_dio_insn_config(struct comedi_device *dev, |
278 | struct comedi_subdevice *s, | |
ddf62f2c HS |
279 | struct comedi_insn *insn, |
280 | unsigned int *data) | |
6baef150 | 281 | { |
72c7692a HS |
282 | /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */ |
283 | int port = s->index == 2 ? 0 : 3; | |
ddf62f2c HS |
284 | int ret; |
285 | ||
286 | ret = comedi_dio_insn_config(dev, s, insn, data, 0); | |
287 | if (ret) | |
288 | return ret; | |
289 | ||
72c7692a HS |
290 | if (data[0] == INSN_CONFIG_DIO_INPUT) |
291 | pcmmio_dio_write(dev, s->io_bits, 0, port); | |
6baef150 CC |
292 | |
293 | return insn->n; | |
294 | } | |
295 | ||
0cc4f3bd HS |
296 | static void pcmmio_reset(struct comedi_device *dev) |
297 | { | |
4edac4a4 HS |
298 | /* Clear all the DIO port bits */ |
299 | pcmmio_dio_write(dev, 0, 0, 0); | |
300 | pcmmio_dio_write(dev, 0, 0, 3); | |
301 | ||
302 | /* Clear all the paged registers */ | |
303 | pcmmio_dio_write(dev, 0, PCMMIO_PAGE_POL, 0); | |
304 | pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0); | |
305 | pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0); | |
6baef150 CC |
306 | } |
307 | ||
748cfd98 | 308 | /* devpriv->spinlock is already locked */ |
b2bb98e1 HS |
309 | static void pcmmio_stop_intr(struct comedi_device *dev, |
310 | struct comedi_subdevice *s) | |
311 | { | |
35c5e884 | 312 | struct pcmmio_private *devpriv = dev->private; |
b2bb98e1 | 313 | |
35c5e884 HS |
314 | devpriv->enabled_mask = 0; |
315 | devpriv->active = 0; | |
76728a93 | 316 | s->async->inttrig = NULL; |
29947fd6 HS |
317 | |
318 | /* disable all dio interrupts */ | |
319 | pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0); | |
b2bb98e1 HS |
320 | } |
321 | ||
967e7e5a HS |
322 | static void pcmmio_handle_dio_intr(struct comedi_device *dev, |
323 | struct comedi_subdevice *s, | |
324 | unsigned int triggered) | |
325 | { | |
326 | struct pcmmio_private *devpriv = dev->private; | |
aecbc172 | 327 | struct comedi_cmd *cmd = &s->async->cmd; |
d7c41e83 | 328 | unsigned int val = 0; |
967e7e5a | 329 | unsigned long flags; |
d7c41e83 | 330 | int i; |
967e7e5a HS |
331 | |
332 | spin_lock_irqsave(&devpriv->spinlock, flags); | |
333 | ||
d7c41e83 HS |
334 | if (!devpriv->active) |
335 | goto done; | |
967e7e5a | 336 | |
d7c41e83 HS |
337 | if (!(triggered & devpriv->enabled_mask)) |
338 | goto done; | |
967e7e5a | 339 | |
aecbc172 HS |
340 | for (i = 0; i < cmd->chanlist_len; i++) { |
341 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); | |
d7c41e83 HS |
342 | |
343 | if (triggered & (1 << chan)) | |
344 | val |= (1 << i); | |
345 | } | |
346 | ||
ff5eb046 | 347 | comedi_buf_write_samples(s, &val, 1); |
d7c41e83 | 348 | |
09959d65 HS |
349 | if (cmd->stop_src == TRIG_COUNT && |
350 | s->async->scans_done >= cmd->stop_arg) | |
351 | s->async->events |= COMEDI_CB_EOA; | |
967e7e5a | 352 | |
d7c41e83 | 353 | done: |
967e7e5a HS |
354 | spin_unlock_irqrestore(&devpriv->spinlock, flags); |
355 | ||
c746db4e | 356 | comedi_handle_events(dev, s); |
967e7e5a HS |
357 | } |
358 | ||
70265d24 | 359 | static irqreturn_t interrupt_pcmmio(int irq, void *d) |
6baef150 | 360 | { |
19e0bf12 | 361 | struct comedi_device *dev = d; |
19e0bf12 | 362 | struct comedi_subdevice *s = dev->read_subdev; |
eacc792d | 363 | unsigned int triggered; |
cd756e3d HS |
364 | unsigned char int_pend; |
365 | ||
eacc792d HS |
366 | /* are there any interrupts pending */ |
367 | int_pend = inb(dev->iobase + PCMMIO_INT_PENDING_REG) & 0x07; | |
368 | if (!int_pend) | |
369 | return IRQ_NONE; | |
6baef150 | 370 | |
eacc792d HS |
371 | /* get, and clear, the pending interrupts */ |
372 | triggered = pcmmio_dio_read(dev, PCMMIO_PAGE_INT_ID, 0); | |
373 | pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0); | |
6baef150 | 374 | |
967e7e5a | 375 | pcmmio_handle_dio_intr(dev, s, triggered); |
b2bb98e1 | 376 | |
b2bb98e1 | 377 | return IRQ_HANDLED; |
6baef150 CC |
378 | } |
379 | ||
748cfd98 | 380 | /* devpriv->spinlock is already locked */ |
22499048 HS |
381 | static void pcmmio_start_intr(struct comedi_device *dev, |
382 | struct comedi_subdevice *s) | |
6baef150 | 383 | { |
35c5e884 | 384 | struct pcmmio_private *devpriv = dev->private; |
c5b970ae HS |
385 | struct comedi_cmd *cmd = &s->async->cmd; |
386 | unsigned int bits = 0; | |
387 | unsigned int pol_bits = 0; | |
388 | int i; | |
20218e6a | 389 | |
c5b970ae HS |
390 | devpriv->enabled_mask = 0; |
391 | devpriv->active = 1; | |
392 | if (cmd->chanlist) { | |
393 | for (i = 0; i < cmd->chanlist_len; i++) { | |
394 | unsigned int chanspec = cmd->chanlist[i]; | |
395 | unsigned int chan = CR_CHAN(chanspec); | |
396 | unsigned int range = CR_RANGE(chanspec); | |
397 | unsigned int aref = CR_AREF(chanspec); | |
398 | ||
399 | bits |= (1 << chan); | |
400 | pol_bits |= (((aref || range) ? 1 : 0) << chan); | |
6baef150 CC |
401 | } |
402 | } | |
c5b970ae HS |
403 | bits &= ((1 << s->n_chan) - 1); |
404 | devpriv->enabled_mask = bits; | |
405 | ||
406 | /* set polarity and enable interrupts */ | |
407 | pcmmio_dio_write(dev, pol_bits, PCMMIO_PAGE_POL, 0); | |
408 | pcmmio_dio_write(dev, bits, PCMMIO_PAGE_ENAB, 0); | |
6baef150 CC |
409 | } |
410 | ||
da91b269 | 411 | static int pcmmio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) |
6baef150 | 412 | { |
35c5e884 | 413 | struct pcmmio_private *devpriv = dev->private; |
6baef150 CC |
414 | unsigned long flags; |
415 | ||
35c5e884 HS |
416 | spin_lock_irqsave(&devpriv->spinlock, flags); |
417 | if (devpriv->active) | |
6baef150 | 418 | pcmmio_stop_intr(dev, s); |
35c5e884 | 419 | spin_unlock_irqrestore(&devpriv->spinlock, flags); |
6baef150 CC |
420 | |
421 | return 0; | |
422 | } | |
423 | ||
725ce0d4 HS |
424 | static int pcmmio_inttrig_start_intr(struct comedi_device *dev, |
425 | struct comedi_subdevice *s, | |
426 | unsigned int trig_num) | |
6baef150 | 427 | { |
35c5e884 | 428 | struct pcmmio_private *devpriv = dev->private; |
725ce0d4 | 429 | struct comedi_cmd *cmd = &s->async->cmd; |
6baef150 | 430 | unsigned long flags; |
6baef150 | 431 | |
725ce0d4 | 432 | if (trig_num != cmd->start_arg) |
6baef150 CC |
433 | return -EINVAL; |
434 | ||
35c5e884 | 435 | spin_lock_irqsave(&devpriv->spinlock, flags); |
76728a93 | 436 | s->async->inttrig = NULL; |
35c5e884 | 437 | if (devpriv->active) |
22499048 | 438 | pcmmio_start_intr(dev, s); |
35c5e884 | 439 | spin_unlock_irqrestore(&devpriv->spinlock, flags); |
6baef150 | 440 | |
6baef150 CC |
441 | return 1; |
442 | } | |
443 | ||
444 | /* | |
445 | * 'do_cmd' function for an 'INTERRUPT' subdevice. | |
446 | */ | |
da91b269 | 447 | static int pcmmio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
6baef150 | 448 | { |
35c5e884 | 449 | struct pcmmio_private *devpriv = dev->private; |
ea6d0d4c | 450 | struct comedi_cmd *cmd = &s->async->cmd; |
6baef150 | 451 | unsigned long flags; |
6baef150 | 452 | |
35c5e884 HS |
453 | spin_lock_irqsave(&devpriv->spinlock, flags); |
454 | devpriv->active = 1; | |
6baef150 | 455 | |
6baef150 | 456 | /* Set up start of acquisition. */ |
725ce0d4 | 457 | if (cmd->start_src == TRIG_INT) |
6baef150 | 458 | s->async->inttrig = pcmmio_inttrig_start_intr; |
725ce0d4 | 459 | else /* TRIG_NOW */ |
22499048 | 460 | pcmmio_start_intr(dev, s); |
725ce0d4 | 461 | |
35c5e884 | 462 | spin_unlock_irqrestore(&devpriv->spinlock, flags); |
6baef150 | 463 | |
6baef150 CC |
464 | return 0; |
465 | } | |
466 | ||
f95d45d1 HS |
467 | static int pcmmio_cmdtest(struct comedi_device *dev, |
468 | struct comedi_subdevice *s, | |
469 | struct comedi_cmd *cmd) | |
6baef150 | 470 | { |
f95d45d1 HS |
471 | int err = 0; |
472 | ||
473 | /* Step 1 : check if triggers are trivially valid */ | |
474 | ||
b21a766f IA |
475 | err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); |
476 | err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); | |
477 | err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); | |
478 | err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
479 | err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
f95d45d1 HS |
480 | |
481 | if (err) | |
482 | return 1; | |
483 | ||
484 | /* Step 2a : make sure trigger sources are unique */ | |
485 | ||
b21a766f IA |
486 | err |= comedi_check_trigger_is_unique(cmd->start_src); |
487 | err |= comedi_check_trigger_is_unique(cmd->stop_src); | |
f95d45d1 HS |
488 | |
489 | /* Step 2b : and mutually compatible */ | |
490 | ||
491 | if (err) | |
492 | return 2; | |
493 | ||
494 | /* Step 3: check if arguments are trivially valid */ | |
495 | ||
b21a766f IA |
496 | err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); |
497 | err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
498 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); | |
499 | err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, | |
500 | cmd->chanlist_len); | |
f95d45d1 | 501 | |
1ea37fd2 | 502 | if (cmd->stop_src == TRIG_COUNT) |
b21a766f | 503 | err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); |
1ea37fd2 | 504 | else /* TRIG_NONE */ |
b21a766f | 505 | err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); |
f95d45d1 HS |
506 | |
507 | if (err) | |
508 | return 3; | |
509 | ||
510 | /* step 4: fix up any arguments */ | |
511 | ||
512 | /* if (err) return 4; */ | |
513 | ||
514 | return 0; | |
6baef150 CC |
515 | } |
516 | ||
6fd13f76 HS |
517 | static int pcmmio_ai_eoc(struct comedi_device *dev, |
518 | struct comedi_subdevice *s, | |
519 | struct comedi_insn *insn, | |
520 | unsigned long context) | |
6baef150 | 521 | { |
7ce69685 HS |
522 | unsigned char status; |
523 | ||
6fd13f76 HS |
524 | status = inb(dev->iobase + PCMMIO_AI_STATUS_REG); |
525 | if (status & PCMMIO_AI_STATUS_DATA_READY) | |
526 | return 0; | |
527 | return -EBUSY; | |
6baef150 CC |
528 | } |
529 | ||
8b0fcb59 HS |
530 | static int pcmmio_ai_insn_read(struct comedi_device *dev, |
531 | struct comedi_subdevice *s, | |
532 | struct comedi_insn *insn, | |
533 | unsigned int *data) | |
6baef150 | 534 | { |
170f13cc | 535 | unsigned long iobase = dev->iobase; |
8b0fcb59 HS |
536 | unsigned int chan = CR_CHAN(insn->chanspec); |
537 | unsigned int range = CR_RANGE(insn->chanspec); | |
538 | unsigned int aref = CR_AREF(insn->chanspec); | |
539 | unsigned char cmd = 0; | |
540 | unsigned int val; | |
7ce69685 | 541 | int ret; |
8b0fcb59 | 542 | int i; |
6baef150 CC |
543 | |
544 | /* | |
8b0fcb59 HS |
545 | * The PCM-MIO uses two Linear Tech LTC1859CG 8-channel A/D converters. |
546 | * The devices use a full duplex serial interface which transmits and | |
547 | * receives data simultaneously. An 8-bit command is shifted into the | |
548 | * ADC interface to configure it for the next conversion. At the same | |
549 | * time, the data from the previous conversion is shifted out of the | |
550 | * device. Consequently, the conversion result is delayed by one | |
551 | * conversion from the command word. | |
552 | * | |
553 | * Setup the cmd for the conversions then do a dummy conversion to | |
554 | * flush the junk data. Then do each conversion requested by the | |
555 | * comedi_insn. Note that the last conversion will leave junk data | |
556 | * in ADC which will get flushed on the next comedi_insn. | |
6baef150 CC |
557 | */ |
558 | ||
8b0fcb59 HS |
559 | if (chan > 7) { |
560 | chan -= 8; | |
2d314558 | 561 | iobase += PCMMIO_AI_2ND_ADC_OFFSET; |
8b0fcb59 | 562 | } |
6baef150 | 563 | |
8b0fcb59 HS |
564 | if (aref == AREF_GROUND) |
565 | cmd |= PCMMIO_AI_CMD_SE; | |
566 | if (chan % 2) | |
567 | cmd |= PCMMIO_AI_CMD_ODD_CHAN; | |
568 | cmd |= PCMMIO_AI_CMD_CHAN_SEL(chan / 2); | |
569 | cmd |= PCMMIO_AI_CMD_RANGE(range); | |
6baef150 | 570 | |
8b0fcb59 | 571 | outb(cmd, iobase + PCMMIO_AI_CMD_REG); |
6fd13f76 HS |
572 | |
573 | ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0); | |
7ce69685 HS |
574 | if (ret) |
575 | return ret; | |
6baef150 | 576 | |
8b0fcb59 HS |
577 | val = inb(iobase + PCMMIO_AI_LSB_REG); |
578 | val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8; | |
6baef150 | 579 | |
8b0fcb59 HS |
580 | for (i = 0; i < insn->n; i++) { |
581 | outb(cmd, iobase + PCMMIO_AI_CMD_REG); | |
6fd13f76 HS |
582 | |
583 | ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0); | |
7ce69685 HS |
584 | if (ret) |
585 | return ret; | |
013f230c | 586 | |
8b0fcb59 HS |
587 | val = inb(iobase + PCMMIO_AI_LSB_REG); |
588 | val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8; | |
5fde0764 HS |
589 | |
590 | /* bipolar data is two's complement */ | |
591 | if (comedi_range_is_bipolar(s, range)) | |
592 | val = comedi_offset_munge(s, val); | |
593 | ||
8b0fcb59 | 594 | data[i] = val; |
6baef150 | 595 | } |
8b0fcb59 HS |
596 | |
597 | return insn->n; | |
6baef150 CC |
598 | } |
599 | ||
6fd13f76 HS |
600 | static int pcmmio_ao_eoc(struct comedi_device *dev, |
601 | struct comedi_subdevice *s, | |
602 | struct comedi_insn *insn, | |
603 | unsigned long context) | |
6baef150 | 604 | { |
6f216c96 | 605 | unsigned char status; |
6baef150 | 606 | |
6fd13f76 HS |
607 | status = inb(dev->iobase + PCMMIO_AO_STATUS_REG); |
608 | if (status & PCMMIO_AO_STATUS_DATA_READY) | |
609 | return 0; | |
610 | return -EBUSY; | |
6baef150 CC |
611 | } |
612 | ||
68533c7b HS |
613 | static int pcmmio_ao_insn_write(struct comedi_device *dev, |
614 | struct comedi_subdevice *s, | |
615 | struct comedi_insn *insn, | |
616 | unsigned int *data) | |
6baef150 | 617 | { |
68533c7b HS |
618 | unsigned long iobase = dev->iobase; |
619 | unsigned int chan = CR_CHAN(insn->chanspec); | |
620 | unsigned int range = CR_RANGE(insn->chanspec); | |
68533c7b | 621 | unsigned char cmd = 0; |
6f216c96 | 622 | int ret; |
68533c7b | 623 | int i; |
6baef150 | 624 | |
68533c7b HS |
625 | /* |
626 | * The PCM-MIO has two Linear Tech LTC2704 DAC devices. Each device | |
627 | * is a 4-channel converter with software-selectable output range. | |
628 | */ | |
6baef150 | 629 | |
68533c7b HS |
630 | if (chan > 3) { |
631 | cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan - 4); | |
632 | iobase += PCMMIO_AO_2ND_DAC_OFFSET; | |
633 | } else { | |
634 | cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan); | |
635 | } | |
013f230c | 636 | |
68533c7b HS |
637 | /* set the range for the channel */ |
638 | outb(PCMMIO_AO_LSB_SPAN(range), iobase + PCMMIO_AO_LSB_REG); | |
639 | outb(0, iobase + PCMMIO_AO_MSB_REG); | |
640 | outb(cmd | PCMMIO_AO_CMD_WR_SPAN_UPDATE, iobase + PCMMIO_AO_CMD_REG); | |
6fd13f76 HS |
641 | |
642 | ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0); | |
6f216c96 HS |
643 | if (ret) |
644 | return ret; | |
013f230c | 645 | |
68533c7b | 646 | for (i = 0; i < insn->n; i++) { |
4b01383a | 647 | unsigned int val = data[i]; |
6baef150 | 648 | |
68533c7b HS |
649 | /* write the data to the channel */ |
650 | outb(val & 0xff, iobase + PCMMIO_AO_LSB_REG); | |
651 | outb((val >> 8) & 0xff, iobase + PCMMIO_AO_MSB_REG); | |
652 | outb(cmd | PCMMIO_AO_CMD_WR_CODE_UPDATE, | |
653 | iobase + PCMMIO_AO_CMD_REG); | |
6fd13f76 HS |
654 | |
655 | ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0); | |
6f216c96 HS |
656 | if (ret) |
657 | return ret; | |
6baef150 | 658 | |
4b01383a | 659 | s->readback[chan] = val; |
6baef150 | 660 | } |
68533c7b HS |
661 | |
662 | return insn->n; | |
6baef150 CC |
663 | } |
664 | ||
b2bb98e1 HS |
665 | static int pcmmio_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
666 | { | |
9a1a6cf8 | 667 | struct pcmmio_private *devpriv; |
b2bb98e1 | 668 | struct comedi_subdevice *s; |
8b6c5694 | 669 | int ret; |
b2bb98e1 | 670 | |
1bdf7c2e HS |
671 | ret = comedi_request_region(dev, it->options[0], 32); |
672 | if (ret) | |
673 | return ret; | |
b2bb98e1 | 674 | |
0bdab509 | 675 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
676 | if (!devpriv) |
677 | return -ENOMEM; | |
b2bb98e1 | 678 | |
83d55bd0 | 679 | spin_lock_init(&devpriv->pagelock); |
5181886e HS |
680 | spin_lock_init(&devpriv->spinlock); |
681 | ||
682 | pcmmio_reset(dev); | |
683 | ||
684 | if (it->options[1]) { | |
685 | ret = request_irq(it->options[1], interrupt_pcmmio, 0, | |
686 | dev->board_name, dev); | |
2eb6b518 | 687 | if (ret == 0) { |
5181886e | 688 | dev->irq = it->options[1]; |
2eb6b518 HS |
689 | |
690 | /* configure the interrupt routing on the board */ | |
691 | outb(PCMMIO_AI_RES_ENA_DIO_RES_ACCESS, | |
692 | dev->iobase + PCMMIO_AI_RES_ENA_REG); | |
693 | outb(PCMMIO_RESOURCE_IRQ(dev->irq), | |
694 | dev->iobase + PCMMIO_RESOURCE_REG); | |
695 | } | |
5181886e | 696 | } |
b2bb98e1 | 697 | |
19e0bf12 | 698 | ret = comedi_alloc_subdevices(dev, 4); |
8b6c5694 HS |
699 | if (ret) |
700 | return ret; | |
b2bb98e1 | 701 | |
170f13cc | 702 | /* Analog Input subdevice */ |
33e101ad | 703 | s = &dev->subdevices[0]; |
170f13cc HS |
704 | s->type = COMEDI_SUBD_AI; |
705 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; | |
706 | s->n_chan = 16; | |
707 | s->maxdata = 0xffff; | |
708 | s->range_table = &pcmmio_ai_ranges; | |
709 | s->insn_read = pcmmio_ai_insn_read; | |
710 | ||
b2bb98e1 | 711 | /* initialize the resource enable register by clearing it */ |
2eb6b518 HS |
712 | outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS, |
713 | dev->iobase + PCMMIO_AI_RES_ENA_REG); | |
714 | outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS, | |
715 | dev->iobase + PCMMIO_AI_RES_ENA_REG + PCMMIO_AI_2ND_ADC_OFFSET); | |
b2bb98e1 | 716 | |
5d4997bb | 717 | /* Analog Output subdevice */ |
33e101ad | 718 | s = &dev->subdevices[1]; |
5d4997bb HS |
719 | s->type = COMEDI_SUBD_AO; |
720 | s->subdev_flags = SDF_READABLE; | |
721 | s->n_chan = 8; | |
722 | s->maxdata = 0xffff; | |
723 | s->range_table = &pcmmio_ao_ranges; | |
5d4997bb | 724 | s->insn_write = pcmmio_ao_insn_write; |
4b01383a HS |
725 | |
726 | ret = comedi_alloc_subdev_readback(s); | |
727 | if (ret) | |
728 | return ret; | |
a81f87e9 | 729 | |
b2bb98e1 | 730 | /* initialize the resource enable register by clearing it */ |
68533c7b HS |
731 | outb(0, dev->iobase + PCMMIO_AO_RESOURCE_ENA_REG); |
732 | outb(0, dev->iobase + PCMMIO_AO_2ND_DAC_OFFSET + | |
733 | PCMMIO_AO_RESOURCE_ENA_REG); | |
b2bb98e1 | 734 | |
19e0bf12 HS |
735 | /* Digital I/O subdevice with interrupt support */ |
736 | s = &dev->subdevices[2]; | |
737 | s->type = COMEDI_SUBD_DIO; | |
738 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
739 | s->n_chan = 24; | |
740 | s->maxdata = 1; | |
741 | s->len_chanlist = 1; | |
742 | s->range_table = &range_digital; | |
743 | s->insn_bits = pcmmio_dio_insn_bits; | |
744 | s->insn_config = pcmmio_dio_insn_config; | |
5181886e HS |
745 | if (dev->irq) { |
746 | dev->read_subdev = s; | |
ff5eb046 | 747 | s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL | SDF_PACKED; |
5181886e HS |
748 | s->len_chanlist = s->n_chan; |
749 | s->cancel = pcmmio_cancel; | |
750 | s->do_cmd = pcmmio_cmd; | |
751 | s->do_cmdtest = pcmmio_cmdtest; | |
752 | } | |
19e0bf12 HS |
753 | |
754 | /* Digital I/O subdevice */ | |
755 | s = &dev->subdevices[3]; | |
756 | s->type = COMEDI_SUBD_DIO; | |
757 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
758 | s->n_chan = 24; | |
759 | s->maxdata = 1; | |
760 | s->range_table = &range_digital; | |
761 | s->insn_bits = pcmmio_dio_insn_bits; | |
762 | s->insn_config = pcmmio_dio_insn_config; | |
b2bb98e1 | 763 | |
748cfd98 | 764 | return 0; |
b2bb98e1 HS |
765 | } |
766 | ||
294f930d | 767 | static struct comedi_driver pcmmio_driver = { |
b2bb98e1 HS |
768 | .driver_name = "pcmmio", |
769 | .module = THIS_MODULE, | |
770 | .attach = pcmmio_attach, | |
0cc9a4e4 | 771 | .detach = comedi_legacy_detach, |
b2bb98e1 | 772 | }; |
294f930d | 773 | module_comedi_driver(pcmmio_driver); |
90f703d3 AT |
774 | |
775 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
500821f3 | 776 | MODULE_DESCRIPTION("Comedi driver for Winsystems PCM-MIO PC/104 board"); |
90f703d3 | 777 | MODULE_LICENSE("GPL"); |