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
157static 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
166static 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
177struct 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
184static 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
206static 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 */
241static 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
276static 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
295static 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 */
308static 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
321static 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
352done:
353 spin_unlock_irqrestore(lock: &devpriv->spinlock, flags);
354
355 comedi_handle_events(dev, s);
356}
357
358static 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 */
380static 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
410static 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
423static 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 */
446static 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
466static 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
516static 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
529static 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
599static 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
612static 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
664static 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
766static struct comedi_driver pcmmio_driver = {
767 .driver_name = "pcmmio",
768 .module = THIS_MODULE,
769 .attach = pcmmio_attach,
770 .detach = comedi_legacy_detach,
771};
772module_comedi_driver(pcmmio_driver);
773
774MODULE_AUTHOR("Comedi https://www.comedi.org");
775MODULE_DESCRIPTION("Comedi driver for Winsystems PCM-MIO PC/104 board");
776MODULE_LICENSE("GPL");
777

source code of linux/drivers/comedi/drivers/pcmmio.c