Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
ii_pci20kc.c
Go to the documentation of this file.
1 /*
2  * comedi/drivers/ii_pci20kc.c
3  * Driver for Intelligent Instruments PCI-20001C carrier board
4  * and modules.
5  *
6  * Copyright (C) 2000 Markus Kempf <[email protected]>
7  * with suggestions from David Schleef
8  * 16.06.2000
9  *
10  * Linux device driver for COMEDI
11  * Intelligent Instrumentation
12  * PCI-20001 C-2A Carrier Board
13  * PCI-20341 M-1A 16-Bit analog input module
14  * - differential
15  * - range (-5V - +5V)
16  * - 16 bit
17  * PCI-20006 M-2 16-Bit analog output module
18  * - ranges (-10V - +10V) (0V - +10V) (-5V - +5V)
19  * - 16 bit
20  *
21  * only ONE PCI-20341 module possible
22  * only ONE PCI-20006 module possible
23  * no extern trigger implemented
24  *
25  * NOT WORKING (but soon) only 4 on-board differential channels supported
26  * NOT WORKING (but soon) only ONE di-port and ONE do-port supported
27  * instead of 4 digital ports
28  * di-port == Port 0
29  * do-port == Port 1
30  *
31  * The state of this driver is only a starting point for a complete
32  * COMEDI-driver. The final driver should support all features of the
33  * carrier board and modules.
34  *
35  * The test configuration:
36  *
37  * kernel 2.2.14 with RTAI v1.2 and patch-2.2.14rthal2
38  * COMEDI 0.7.45
39  * COMEDILIB 0.7.9
40  *
41  */
42 /*
43 Driver: ii_pci20kc
44 Description: Intelligent Instruments PCI-20001C carrier board
45 Author: Markus Kempf <[email protected]>
46 Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc)
47 Status: works
48 
49 Supports the PCI-20001 C-2a Carrier board, and could probably support
50 the other carrier boards with small modifications. Modules supported
51 are:
52  PCI-20006 M-2 16-bit analog output module
53  PCI-20341 M-1A 16-bit analog input module
54 
55 Options:
56  0 Board base address
57  1 IRQ
58  2 first option for module 1
59  3 second option for module 1
60  4 first option for module 2
61  5 second option for module 2
62  6 first option for module 3
63  7 second option for module 3
64 
65 options for PCI-20006M:
66  first: Analog output channel 0 range configuration
67  0 bipolar 10 (-10V -- +10V)
68  1 unipolar 10 (0V -- +10V)
69  2 bipolar 5 (-5V -- 5V)
70  second: Analog output channel 1 range configuration
71 
72 options for PCI-20341M:
73  first: Analog input gain configuration
74  0 1
75  1 10
76  2 100
77  3 200
78 */
79 
80 /* XXX needs to use ioremap() for compatibility with 2.4 kernels. Should also
81  * check_mem_region() etc. - fmhess */
82 
83 #include "../comedidev.h"
84 
85 #define PCI20000_ID 0x1d
86 #define PCI20341_ID 0x77
87 #define PCI20006_ID 0xe3
88 #define PCI20xxx_EMPTY_ID 0xff
89 
90 #define PCI20000_OFFSET 0x100
91 #define PCI20000_MODULES 3
92 
93 #define PCI20000_DIO_0 0x80
94 #define PCI20000_DIO_1 0x81
95 #define PCI20000_DIO_2 0xc0
96 #define PCI20000_DIO_3 0xc1
97 #define PCI20000_DIO_CONTROL_01 0x83 /* port 0, 1 control */
98 #define PCI20000_DIO_CONTROL_23 0xc3 /* port 2, 3 control */
99 #define PCI20000_DIO_BUFFER 0x82 /* buffer direction & enable */
100 #define PCI20000_DIO_EOC 0xef /* even port, control output */
101 #define PCI20000_DIO_OOC 0xfd /* odd port, control output */
102 #define PCI20000_DIO_EIC 0x90 /* even port, control input */
103 #define PCI20000_DIO_OIC 0x82 /* odd port, control input */
104 #define DIO_CAND 0x12 /* and bit 1 & 4 of control */
105 #define DIO_BE 0x01 /* buffer: port enable */
106 #define DIO_BO 0x04 /* buffer: output */
107 #define DIO_BI 0x05 /* buffer: input */
108 #define DIO_PS_0 0x00 /* buffer: port shift 0 */
109 #define DIO_PS_1 0x01 /* buffer: port shift 1 */
110 #define DIO_PS_2 0x04 /* buffer: port shift 2 */
111 #define DIO_PS_3 0x05 /* buffer: port shift 3 */
112 
113 #define PCI20006_LCHAN0 0x0d
114 #define PCI20006_STROBE0 0x0b
115 #define PCI20006_LCHAN1 0x15
116 #define PCI20006_STROBE1 0x13
117 
118 #define PCI20341_INIT 0x04
119 #define PCI20341_REPMODE 0x00 /* single shot mode */
120 #define PCI20341_PACER 0x00 /* Hardware Pacer disabled */
121 #define PCI20341_CHAN_NR 0x04 /* number of input channels */
122 #define PCI20341_CONFIG_REG 0x10
123 #define PCI20341_MOD_STATUS 0x01
124 #define PCI20341_OPT_REG 0x11
125 #define PCI20341_SET_TIME_REG 0x15
126 #define PCI20341_LCHAN_ADDR_REG 0x13
127 #define PCI20341_CHAN_LIST 0x80
128 #define PCI20341_CC_RESET 0x1b
129 #define PCI20341_CHAN_RESET 0x19
130 #define PCI20341_SOFT_PACER 0x04
131 #define PCI20341_STATUS_REG 0x12
132 #define PCI20341_LDATA 0x02
133 #define PCI20341_DAISY_CHAIN 0x20 /* On-board inputs only */
134 #define PCI20341_MUX 0x04 /* Enable on-board MUX */
135 #define PCI20341_SCANLIST 0x80 /* Channel/Gain Scan List */
136 
139  struct {
140  void __iomem *iobase;
141  const struct comedi_lrange *ao_range_list[2];
142  /* range of channels of ao module */
143  unsigned int last_data[2];
144  } pci20006;
145  struct {
146  void __iomem *iobase;
147  int timebase;
149  int ai_gain;
150  } pci20341;
151 };
152 
154 
157 };
158 
159 #define devpriv ((struct pci20xxx_private *)dev->private)
160 #define CHAN (CR_CHAN(it->chanlist[0]))
161 
162 static int pci20006_init(struct comedi_device *dev, struct comedi_subdevice *s,
163  int opt0, int opt1);
164 static int pci20341_init(struct comedi_device *dev, struct comedi_subdevice *s,
165  int opt0, int opt1);
166 static int pci20xxx_dio_init(struct comedi_device *dev,
167  struct comedi_subdevice *s);
168 
169 /*
170  options[0] Board base address
171  options[1] IRQ
172  options[2] first option for module 1
173  options[3] second option for module 1
174  options[4] first option for module 2
175  options[5] second option for module 2
176  options[6] first option for module 3
177  options[7] second option for module 3
178 
179  options for PCI-20341M:
180  first Analog input gain configuration
181  0 == 1
182  1 == 10
183  2 == 100
184  3 == 200
185 
186  options for PCI-20006M:
187  first Analog output channel 0 range configuration
188  0 == bipolar 10 (-10V -- +10V)
189  1 == unipolar 10V (0V -- +10V)
190  2 == bipolar 5V (-5V -- +5V)
191  second Analog output channel 1 range configuration
192  0 == bipolar 10 (-10V -- +10V)
193  1 == unipolar 10V (0V -- +10V)
194  2 == bipolar 5V (-5V -- +5V)
195 */
196 static int pci20xxx_attach(struct comedi_device *dev,
197  struct comedi_devconfig *it)
198 {
199  unsigned char i;
200  int ret;
201  int id;
202  struct comedi_subdevice *s;
203  union pci20xxx_subdev_private *sdp;
204 
206  if (ret)
207  return ret;
208 
209  ret = alloc_private(dev, sizeof(struct pci20xxx_private));
210  if (ret < 0)
211  return ret;
212 
213  devpriv->ioaddr = (void __iomem *)(unsigned long)it->options[0];
214  dev->board_name = "pci20kc";
215 
216  /* Check PCI-20001 C-2A Carrier Board ID */
217  if ((readb(devpriv->ioaddr) & PCI20000_ID) != PCI20000_ID) {
218  printk(KERN_WARNING "comedi%d: ii_pci20kc PCI-20001"
219  " C-2A Carrier Board at base=0x%p not found !\n",
220  dev->minor, devpriv->ioaddr);
221  return -EINVAL;
222  }
223  printk(KERN_INFO "comedi%d: ii_pci20kc: PCI-20001 C-2A at base=0x%p\n",
224  dev->minor, devpriv->ioaddr);
225 
226  for (i = 0; i < PCI20000_MODULES; i++) {
227  s = &dev->subdevices[i];
228  id = readb(devpriv->ioaddr + (i + 1) * PCI20000_OFFSET);
229  s->private = devpriv->subdev_private + i;
230  sdp = s->private;
231  switch (id) {
232  case PCI20006_ID:
233  sdp->pci20006.iobase =
234  devpriv->ioaddr + (i + 1) * PCI20000_OFFSET;
235  pci20006_init(dev, s, it->options[2 * i + 2],
236  it->options[2 * i + 3]);
237  printk(KERN_INFO "comedi%d: "
238  "ii_pci20kc PCI-20006 module in slot %d\n",
239  dev->minor, i + 1);
240  break;
241  case PCI20341_ID:
242  sdp->pci20341.iobase =
243  devpriv->ioaddr + (i + 1) * PCI20000_OFFSET;
244  pci20341_init(dev, s, it->options[2 * i + 2],
245  it->options[2 * i + 3]);
246  printk(KERN_INFO "comedi%d: "
247  "ii_pci20kc PCI-20341 module in slot %d\n",
248  dev->minor, i + 1);
249  break;
250  default:
251  printk(KERN_WARNING "ii_pci20kc: unknown module "
252  "code 0x%02x in slot %d: module disabled\n",
253  id, i); /* XXX this looks like a bug! i + 1 ?? */
254  /* fall through */
255  case PCI20xxx_EMPTY_ID:
257  break;
258  }
259  }
260 
261  /* initialize struct pci20xxx_private */
262  pci20xxx_dio_init(dev, &dev->subdevices[PCI20000_MODULES]);
263 
264  return 1;
265 }
266 
267 static void pci20xxx_detach(struct comedi_device *dev)
268 {
269  /* Nothing to cleanup */
270 }
271 
272 /* pci20006m */
273 
274 static int pci20006_insn_read(struct comedi_device *dev,
275  struct comedi_subdevice *s,
276  struct comedi_insn *insn, unsigned int *data);
277 static int pci20006_insn_write(struct comedi_device *dev,
278  struct comedi_subdevice *s,
279  struct comedi_insn *insn, unsigned int *data);
280 
281 static const struct comedi_lrange *pci20006_range_list[] = {
285 };
286 
287 static int pci20006_init(struct comedi_device *dev, struct comedi_subdevice *s,
288  int opt0, int opt1)
289 {
290  union pci20xxx_subdev_private *sdp = s->private;
291 
292  if (opt0 < 0 || opt0 > 2)
293  opt0 = 0;
294  if (opt1 < 0 || opt1 > 2)
295  opt1 = 0;
296 
297  sdp->pci20006.ao_range_list[0] = pci20006_range_list[opt0];
298  sdp->pci20006.ao_range_list[1] = pci20006_range_list[opt1];
299 
300  /* ao subdevice */
301  s->type = COMEDI_SUBD_AO;
303  s->n_chan = 2;
304  s->len_chanlist = 2;
305  s->insn_read = pci20006_insn_read;
306  s->insn_write = pci20006_insn_write;
307  s->maxdata = 0xffff;
308  s->range_table_list = sdp->pci20006.ao_range_list;
309  return 0;
310 }
311 
312 static int pci20006_insn_read(struct comedi_device *dev,
313  struct comedi_subdevice *s,
314  struct comedi_insn *insn, unsigned int *data)
315 {
316  union pci20xxx_subdev_private *sdp = s->private;
317 
318  data[0] = sdp->pci20006.last_data[CR_CHAN(insn->chanspec)];
319 
320  return 1;
321 }
322 
323 static int pci20006_insn_write(struct comedi_device *dev,
324  struct comedi_subdevice *s,
325  struct comedi_insn *insn, unsigned int *data)
326 {
327  union pci20xxx_subdev_private *sdp = s->private;
328  int hi, lo;
329  unsigned int boarddata;
330 
331  sdp->pci20006.last_data[CR_CHAN(insn->chanspec)] = data[0];
332  boarddata = (((unsigned int)data[0] + 0x8000) & 0xffff);
333  /* comedi-data -> board-data */
334  lo = (boarddata & 0xff);
335  hi = ((boarddata >> 8) & 0xff);
336 
337  switch (CR_CHAN(insn->chanspec)) {
338  case 0:
339  writeb(lo, sdp->iobase + PCI20006_LCHAN0);
340  writeb(hi, sdp->iobase + PCI20006_LCHAN0 + 1);
341  writeb(0x00, sdp->iobase + PCI20006_STROBE0);
342  break;
343  case 1:
344  writeb(lo, sdp->iobase + PCI20006_LCHAN1);
345  writeb(hi, sdp->iobase + PCI20006_LCHAN1 + 1);
346  writeb(0x00, sdp->iobase + PCI20006_STROBE1);
347  break;
348  default:
350  " comedi%d: pci20xxx: ao channel Error!\n", dev->minor);
351  return -EINVAL;
352  }
353 
354  return 1;
355 }
356 
357 /* PCI20341M */
358 
359 static int pci20341_insn_read(struct comedi_device *dev,
360  struct comedi_subdevice *s,
361  struct comedi_insn *insn, unsigned int *data);
362 
363 static const int pci20341_timebase[] = { 0x00, 0x00, 0x00, 0x04 };
364 static const int pci20341_settling_time[] = { 0x58, 0x58, 0x93, 0x99 };
365 
366 static const struct comedi_lrange range_bipolar0_5 = {
367  1,
368  {BIP_RANGE(0.5)}
369 };
370 
371 static const struct comedi_lrange range_bipolar0_05 = {
372  1,
373  {BIP_RANGE(0.05)}
374 };
375 
376 static const struct comedi_lrange range_bipolar0_025 = {
377  1,
378  {BIP_RANGE(0.025)}
379 };
380 
381 static const struct comedi_lrange *const pci20341_ranges[] = {
383  &range_bipolar0_5,
384  &range_bipolar0_05,
385  &range_bipolar0_025,
386 };
387 
388 static int pci20341_init(struct comedi_device *dev, struct comedi_subdevice *s,
389  int opt0, int opt1)
390 {
391  union pci20xxx_subdev_private *sdp = s->private;
392  int option;
393 
394  /* options handling */
395  if (opt0 < 0 || opt0 > 3)
396  opt0 = 0;
397  sdp->pci20341.timebase = pci20341_timebase[opt0];
398  sdp->pci20341.settling_time = pci20341_settling_time[opt0];
399 
400  /* ai subdevice */
401  s->type = COMEDI_SUBD_AI;
405  s->insn_read = pci20341_insn_read;
406  s->maxdata = 0xffff;
407  s->range_table = pci20341_ranges[opt0];
408 
409  /* depends on gain, trigger, repetition mode */
410  option = sdp->pci20341.timebase | PCI20341_REPMODE;
411 
412  /* initialize Module */
414  /* set Pacer */
416  /* option register */
417  writeb(option, sdp->iobase + PCI20341_OPT_REG);
418  /* settling time counter */
419  writeb(sdp->pci20341.settling_time,
421  /* trigger not implemented */
422  return 0;
423 }
424 
425 static int pci20341_insn_read(struct comedi_device *dev,
426  struct comedi_subdevice *s,
427  struct comedi_insn *insn, unsigned int *data)
428 {
429  union pci20xxx_subdev_private *sdp = s->private;
430  unsigned int i = 0, j = 0;
431  int lo, hi;
432  unsigned char eoc; /* end of conversion */
433  unsigned int clb; /* channel list byte */
434  unsigned int boarddata;
435 
436  /* write number of input channels */
438  clb = PCI20341_DAISY_CHAIN | PCI20341_MUX | (sdp->pci20341.ai_gain << 3)
439  | CR_CHAN(insn->chanspec);
440  writeb(clb, sdp->iobase + PCI20341_CHAN_LIST);
441 
442  /* reset settling time counter and trigger delay counter */
443  writeb(0x00, sdp->iobase + PCI20341_CC_RESET);
444 
445  writeb(0x00, sdp->iobase + PCI20341_CHAN_RESET);
446 
447  /* generate Pacer */
448 
449  for (i = 0; i < insn->n; i++) {
450  /* data polling isn't the niciest way to get the data, I know,
451  * but there are only 6 cycles (mean) and it is easier than
452  * the whole interrupt stuff
453  */
454  j = 0;
455  /* generate Pacer */
457 
458  eoc = readb(sdp->iobase + PCI20341_STATUS_REG);
459  /* poll Interrupt Flag */
460  while ((eoc < 0x80) && j < 100) {
461  j++;
462  eoc = readb(sdp->iobase + PCI20341_STATUS_REG);
463  }
464  if (j >= 100) {
466  "comedi%d: pci20xxx: "
467  "AI interrupt channel %i polling exit !\n",
468  dev->minor, i);
469  return -EINVAL;
470  }
471  lo = readb(sdp->iobase + PCI20341_LDATA);
472  hi = readb(sdp->iobase + PCI20341_LDATA + 1);
473  boarddata = lo + 0x100 * hi;
474 
475  /* board-data -> comedi-data */
476  data[i] = (short)((boarddata + 0x8000) & 0xffff);
477  }
478 
479  return i;
480 }
481 
482 /* native DIO */
483 
484 static void pci20xxx_dio_config(struct comedi_device *dev,
485  struct comedi_subdevice *s);
486 static int pci20xxx_dio_insn_bits(struct comedi_device *dev,
487  struct comedi_subdevice *s,
488  struct comedi_insn *insn, unsigned int *data);
489 static int pci20xxx_dio_insn_config(struct comedi_device *dev,
490  struct comedi_subdevice *s,
491  struct comedi_insn *insn,
492  unsigned int *data);
493 
494 /* initialize struct pci20xxx_private */
495 static int pci20xxx_dio_init(struct comedi_device *dev,
496  struct comedi_subdevice *s)
497 {
498 
499  s->type = COMEDI_SUBD_DIO;
501  s->n_chan = 32;
502  s->insn_bits = pci20xxx_dio_insn_bits;
503  s->insn_config = pci20xxx_dio_insn_config;
504  s->maxdata = 1;
505  s->len_chanlist = 32;
507  s->io_bits = 0;
508 
509  /* digital I/O lines default to input on board reset. */
510  pci20xxx_dio_config(dev, s);
511 
512  return 0;
513 }
514 
515 static int pci20xxx_dio_insn_config(struct comedi_device *dev,
516  struct comedi_subdevice *s,
517  struct comedi_insn *insn,
518  unsigned int *data)
519 {
520  int mask, bits;
521 
522  mask = 1 << CR_CHAN(insn->chanspec);
523  if (mask & 0x000000ff)
524  bits = 0x000000ff;
525  else if (mask & 0x0000ff00)
526  bits = 0x0000ff00;
527  else if (mask & 0x00ff0000)
528  bits = 0x00ff0000;
529  else
530  bits = 0xff000000;
531  if (data[0])
532  s->io_bits |= bits;
533  else
534  s->io_bits &= ~bits;
535  pci20xxx_dio_config(dev, s);
536 
537  return 1;
538 }
539 
540 static int pci20xxx_dio_insn_bits(struct comedi_device *dev,
541  struct comedi_subdevice *s,
542  struct comedi_insn *insn, unsigned int *data)
543 {
544  unsigned int mask = data[0];
545 
546  s->state &= ~mask;
547  s->state |= (mask & data[1]);
548 
549  mask &= s->io_bits;
550  if (mask & 0x000000ff)
551  writeb((s->state >> 0) & 0xff,
552  devpriv->ioaddr + PCI20000_DIO_0);
553  if (mask & 0x0000ff00)
554  writeb((s->state >> 8) & 0xff,
555  devpriv->ioaddr + PCI20000_DIO_1);
556  if (mask & 0x00ff0000)
557  writeb((s->state >> 16) & 0xff,
558  devpriv->ioaddr + PCI20000_DIO_2);
559  if (mask & 0xff000000)
560  writeb((s->state >> 24) & 0xff,
561  devpriv->ioaddr + PCI20000_DIO_3);
562 
563  data[1] = readb(devpriv->ioaddr + PCI20000_DIO_0);
564  data[1] |= readb(devpriv->ioaddr + PCI20000_DIO_1) << 8;
565  data[1] |= readb(devpriv->ioaddr + PCI20000_DIO_2) << 16;
566  data[1] |= readb(devpriv->ioaddr + PCI20000_DIO_3) << 24;
567 
568  return insn->n;
569 }
570 
571 static void pci20xxx_dio_config(struct comedi_device *dev,
572  struct comedi_subdevice *s)
573 {
574  unsigned char control_01;
575  unsigned char control_23;
576  unsigned char buffer;
577 
578  control_01 = readb(devpriv->ioaddr + PCI20000_DIO_CONTROL_01);
579  control_23 = readb(devpriv->ioaddr + PCI20000_DIO_CONTROL_23);
580  buffer = readb(devpriv->ioaddr + PCI20000_DIO_BUFFER);
581 
582  if (s->io_bits & 0x000000ff) {
583  /* output port 0 */
584  control_01 &= PCI20000_DIO_EOC;
585  buffer = (buffer & (~(DIO_BE << DIO_PS_0))) | (DIO_BO <<
586  DIO_PS_0);
587  } else {
588  /* input port 0 */
589  control_01 = (control_01 & DIO_CAND) | PCI20000_DIO_EIC;
590  buffer = (buffer & (~(DIO_BI << DIO_PS_0)));
591  }
592  if (s->io_bits & 0x0000ff00) {
593  /* output port 1 */
594  control_01 &= PCI20000_DIO_OOC;
595  buffer = (buffer & (~(DIO_BE << DIO_PS_1))) | (DIO_BO <<
596  DIO_PS_1);
597  } else {
598  /* input port 1 */
599  control_01 = (control_01 & DIO_CAND) | PCI20000_DIO_OIC;
600  buffer = (buffer & (~(DIO_BI << DIO_PS_1)));
601  }
602  if (s->io_bits & 0x00ff0000) {
603  /* output port 2 */
604  control_23 &= PCI20000_DIO_EOC;
605  buffer = (buffer & (~(DIO_BE << DIO_PS_2))) | (DIO_BO <<
606  DIO_PS_2);
607  } else {
608  /* input port 2 */
609  control_23 = (control_23 & DIO_CAND) | PCI20000_DIO_EIC;
610  buffer = (buffer & (~(DIO_BI << DIO_PS_2)));
611  }
612  if (s->io_bits & 0xff000000) {
613  /* output port 3 */
614  control_23 &= PCI20000_DIO_OOC;
615  buffer = (buffer & (~(DIO_BE << DIO_PS_3))) | (DIO_BO <<
616  DIO_PS_3);
617  } else {
618  /* input port 3 */
619  control_23 = (control_23 & DIO_CAND) | PCI20000_DIO_OIC;
620  buffer = (buffer & (~(DIO_BI << DIO_PS_3)));
621  }
622  writeb(control_01, devpriv->ioaddr + PCI20000_DIO_CONTROL_01);
623  writeb(control_23, devpriv->ioaddr + PCI20000_DIO_CONTROL_23);
624  writeb(buffer, devpriv->ioaddr + PCI20000_DIO_BUFFER);
625 }
626 
627 #if 0
628 static void pci20xxx_do(struct comedi_device *dev, struct comedi_subdevice *s)
629 {
630  /* XXX if the channel is configured for input, does this
631  do bad things? */
632  /* XXX it would be a good idea to only update the registers
633  that _need_ to be updated. This requires changes to
634  comedi, however. */
635  writeb((s->state >> 0) & 0xff, devpriv->ioaddr + PCI20000_DIO_0);
636  writeb((s->state >> 8) & 0xff, devpriv->ioaddr + PCI20000_DIO_1);
637  writeb((s->state >> 16) & 0xff, devpriv->ioaddr + PCI20000_DIO_2);
638  writeb((s->state >> 24) & 0xff, devpriv->ioaddr + PCI20000_DIO_3);
639 }
640 
641 static unsigned int pci20xxx_di(struct comedi_device *dev,
642  struct comedi_subdevice *s)
643 {
644  /* XXX same note as above */
645  unsigned int bits;
646 
647  bits = readb(devpriv->ioaddr + PCI20000_DIO_0);
648  bits |= readb(devpriv->ioaddr + PCI20000_DIO_1) << 8;
649  bits |= readb(devpriv->ioaddr + PCI20000_DIO_2) << 16;
650  bits |= readb(devpriv->ioaddr + PCI20000_DIO_3) << 24;
651 
652  return bits;
653 }
654 #endif
655 
656 static struct comedi_driver pci20xxx_driver = {
657  .driver_name = "ii_pci20kc",
658  .module = THIS_MODULE,
659  .attach = pci20xxx_attach,
660  .detach = pci20xxx_detach,
661 };
662 module_comedi_driver(pci20xxx_driver);
663 
664 MODULE_AUTHOR("Comedi http://www.comedi.org");
665 MODULE_DESCRIPTION("Comedi low-level driver");
666 MODULE_LICENSE("GPL");