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