1 // SPDX-License-Identifier: GPL-2.0+
4 * Diamond Systems Diamond-MM-32-AT Comedi driver
6 * COMEDI - Linux Control and Measurement Device Interface
7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
12 * Description: Diamond Systems Diamond-MM-32-AT
13 * Devices: [Diamond Systems] Diamond-MM-32-AT (dmm32at)
14 * Author: Perry J. Piplani <perry.j.piplani@nasa.gov>
15 * Updated: Fri Jun 4 09:13:24 CDT 2004
16 * Status: experimental
18 * Configuration Options:
19 * comedi_config /dev/comedi0 dmm32at baseaddr,irq
21 * This driver is for the Diamond Systems MM-32-AT board
22 * http://www.diamondsystems.com/products/diamondmm32at
24 * It is being used on several projects inside NASA, without
25 * problems so far. For analog input commands, TRIG_EXT is not
29 #include <linux/module.h>
30 #include <linux/delay.h>
31 #include <linux/interrupt.h>
32 #include "../comedidev.h"
36 /* Board register addresses */
37 #define DMM32AT_AI_START_CONV_REG 0x00
38 #define DMM32AT_AI_LSB_REG 0x00
39 #define DMM32AT_AUX_DOUT_REG 0x01
40 #define DMM32AT_AUX_DOUT2 BIT(2) /* J3.42 - OUT2 (OUT2EN) */
41 #define DMM32AT_AUX_DOUT1 BIT(1) /* J3.43 */
42 #define DMM32AT_AUX_DOUT0 BIT(0) /* J3.44 - OUT0 (OUT0EN) */
43 #define DMM32AT_AI_MSB_REG 0x01
44 #define DMM32AT_AI_LO_CHAN_REG 0x02
45 #define DMM32AT_AI_HI_CHAN_REG 0x03
46 #define DMM32AT_AUX_DI_REG 0x04
47 #define DMM32AT_AUX_DI_DACBUSY BIT(7)
48 #define DMM32AT_AUX_DI_CALBUSY BIT(6)
49 #define DMM32AT_AUX_DI3 BIT(3) /* J3.45 - ADCLK (CLKSEL) */
50 #define DMM32AT_AUX_DI2 BIT(2) /* J3.46 - GATE12 (GT12EN) */
51 #define DMM32AT_AUX_DI1 BIT(1) /* J3.47 - GATE0 (GT0EN) */
52 #define DMM32AT_AUX_DI0 BIT(0) /* J3.48 - CLK0 (SRC0) */
53 #define DMM32AT_AO_LSB_REG 0x04
54 #define DMM32AT_AO_MSB_REG 0x05
55 #define DMM32AT_AO_MSB_DACH(x) ((x) << 6)
56 #define DMM32AT_FIFO_DEPTH_REG 0x06
57 #define DMM32AT_FIFO_CTRL_REG 0x07
58 #define DMM32AT_FIFO_CTRL_FIFOEN BIT(3)
59 #define DMM32AT_FIFO_CTRL_SCANEN BIT(2)
60 #define DMM32AT_FIFO_CTRL_FIFORST BIT(1)
61 #define DMM32AT_FIFO_STATUS_REG 0x07
62 #define DMM32AT_FIFO_STATUS_EF BIT(7)
63 #define DMM32AT_FIFO_STATUS_HF BIT(6)
64 #define DMM32AT_FIFO_STATUS_FF BIT(5)
65 #define DMM32AT_FIFO_STATUS_OVF BIT(4)
66 #define DMM32AT_FIFO_STATUS_FIFOEN BIT(3)
67 #define DMM32AT_FIFO_STATUS_SCANEN BIT(2)
68 #define DMM32AT_FIFO_STATUS_PAGE_MASK (3 << 0)
69 #define DMM32AT_CTRL_REG 0x08
70 #define DMM32AT_CTRL_RESETA BIT(5)
71 #define DMM32AT_CTRL_RESETD BIT(4)
72 #define DMM32AT_CTRL_INTRST BIT(3)
73 #define DMM32AT_CTRL_PAGE(x) ((x) << 0)
74 #define DMM32AT_CTRL_PAGE_8254 DMM32AT_CTRL_PAGE(0)
75 #define DMM32AT_CTRL_PAGE_8255 DMM32AT_CTRL_PAGE(1)
76 #define DMM32AT_CTRL_PAGE_CALIB DMM32AT_CTRL_PAGE(3)
77 #define DMM32AT_AI_STATUS_REG 0x08
78 #define DMM32AT_AI_STATUS_STS BIT(7)
79 #define DMM32AT_AI_STATUS_SD1 BIT(6)
80 #define DMM32AT_AI_STATUS_SD0 BIT(5)
81 #define DMM32AT_AI_STATUS_ADCH_MASK (0x1f << 0)
82 #define DMM32AT_INTCLK_REG 0x09
83 #define DMM32AT_INTCLK_ADINT BIT(7)
84 #define DMM32AT_INTCLK_DINT BIT(6)
85 #define DMM32AT_INTCLK_TINT BIT(5)
86 #define DMM32AT_INTCLK_CLKEN BIT(1) /* 1=see below 0=software */
87 #define DMM32AT_INTCLK_CLKSEL BIT(0) /* 1=OUT2 0=EXTCLK */
88 #define DMM32AT_CTRDIO_CFG_REG 0x0a
89 #define DMM32AT_CTRDIO_CFG_FREQ12 BIT(7) /* CLK12 1=100KHz 0=10MHz */
90 #define DMM32AT_CTRDIO_CFG_FREQ0 BIT(6) /* CLK0 1=10KHz 0=10MHz */
91 #define DMM32AT_CTRDIO_CFG_OUT2EN BIT(5) /* J3.42 1=OUT2 is DOUT2 */
92 #define DMM32AT_CTRDIO_CFG_OUT0EN BIT(4) /* J3,44 1=OUT0 is DOUT0 */
93 #define DMM32AT_CTRDIO_CFG_GT0EN BIT(2) /* J3.47 1=DIN1 is GATE0 */
94 #define DMM32AT_CTRDIO_CFG_SRC0 BIT(1) /* CLK0 is 0=FREQ0 1=J3.48 */
95 #define DMM32AT_CTRDIO_CFG_GT12EN BIT(0) /* J3.46 1=DIN2 is GATE12 */
96 #define DMM32AT_AI_CFG_REG 0x0b
97 #define DMM32AT_AI_CFG_SCINT(x) ((x) << 4)
98 #define DMM32AT_AI_CFG_SCINT_20US DMM32AT_AI_CFG_SCINT(0)
99 #define DMM32AT_AI_CFG_SCINT_15US DMM32AT_AI_CFG_SCINT(1)
100 #define DMM32AT_AI_CFG_SCINT_10US DMM32AT_AI_CFG_SCINT(2)
101 #define DMM32AT_AI_CFG_SCINT_5US DMM32AT_AI_CFG_SCINT(3)
102 #define DMM32AT_AI_CFG_RANGE BIT(3) /* 0=5V 1=10V */
103 #define DMM32AT_AI_CFG_ADBU BIT(2) /* 0=bipolar 1=unipolar */
104 #define DMM32AT_AI_CFG_GAIN(x) ((x) << 0)
105 #define DMM32AT_AI_READBACK_REG 0x0b
106 #define DMM32AT_AI_READBACK_WAIT BIT(7) /* DMM32AT_AI_STATUS_STS */
107 #define DMM32AT_AI_READBACK_RANGE BIT(3)
108 #define DMM32AT_AI_READBACK_ADBU BIT(2)
109 #define DMM32AT_AI_READBACK_GAIN_MASK (3 << 0)
111 #define DMM32AT_CLK1 0x0d
112 #define DMM32AT_CLK2 0x0e
113 #define DMM32AT_CLKCT 0x0f
115 #define DMM32AT_8255_IOBASE 0x0c /* Page 1 registers */
117 /* Board register values. */
119 /* DMM32AT_AI_CFG_REG 0x0b */
120 #define DMM32AT_RANGE_U10 0x0c
121 #define DMM32AT_RANGE_U5 0x0d
122 #define DMM32AT_RANGE_B10 0x08
123 #define DMM32AT_RANGE_B5 0x00
125 /* DMM32AT_CLKCT 0x0f */
126 #define DMM32AT_CLKCT1 0x56 /* mode3 counter 1 - write low byte only */
127 #define DMM32AT_CLKCT2 0xb6 /* mode3 counter 2 - write high and low byte */
129 /* board AI ranges in comedi structure */
130 static const struct comedi_lrange dmm32at_airanges
= {
139 /* register values for above ranges */
140 static const unsigned char dmm32at_rangebits
[] = {
147 /* only one of these ranges is valid, as set by a jumper on the
148 * board. The application should only use the range set by the jumper
150 static const struct comedi_lrange dmm32at_aoranges
= {
159 static void dmm32at_ai_set_chanspec(struct comedi_device
*dev
,
160 struct comedi_subdevice
*s
,
161 unsigned int chanspec
, int nchan
)
163 unsigned int chan
= CR_CHAN(chanspec
);
164 unsigned int range
= CR_RANGE(chanspec
);
165 unsigned int last_chan
= (chan
+ nchan
- 1) % s
->n_chan
;
167 outb(DMM32AT_FIFO_CTRL_FIFORST
, dev
->iobase
+ DMM32AT_FIFO_CTRL_REG
);
170 outb(DMM32AT_FIFO_CTRL_SCANEN
,
171 dev
->iobase
+ DMM32AT_FIFO_CTRL_REG
);
173 outb(chan
, dev
->iobase
+ DMM32AT_AI_LO_CHAN_REG
);
174 outb(last_chan
, dev
->iobase
+ DMM32AT_AI_HI_CHAN_REG
);
175 outb(dmm32at_rangebits
[range
], dev
->iobase
+ DMM32AT_AI_CFG_REG
);
178 static unsigned int dmm32at_ai_get_sample(struct comedi_device
*dev
,
179 struct comedi_subdevice
*s
)
183 val
= inb(dev
->iobase
+ DMM32AT_AI_LSB_REG
);
184 val
|= (inb(dev
->iobase
+ DMM32AT_AI_MSB_REG
) << 8);
186 /* munge two's complement value to offset binary */
187 return comedi_offset_munge(s
, val
);
190 static int dmm32at_ai_status(struct comedi_device
*dev
,
191 struct comedi_subdevice
*s
,
192 struct comedi_insn
*insn
,
193 unsigned long context
)
195 unsigned char status
;
197 status
= inb(dev
->iobase
+ context
);
198 if ((status
& DMM32AT_AI_STATUS_STS
) == 0)
203 static int dmm32at_ai_insn_read(struct comedi_device
*dev
,
204 struct comedi_subdevice
*s
,
205 struct comedi_insn
*insn
,
211 dmm32at_ai_set_chanspec(dev
, s
, insn
->chanspec
, 1);
213 /* wait for circuit to settle */
214 ret
= comedi_timeout(dev
, s
, insn
, dmm32at_ai_status
,
215 DMM32AT_AI_READBACK_REG
);
219 for (i
= 0; i
< insn
->n
; i
++) {
220 outb(0xff, dev
->iobase
+ DMM32AT_AI_START_CONV_REG
);
222 ret
= comedi_timeout(dev
, s
, insn
, dmm32at_ai_status
,
223 DMM32AT_AI_STATUS_REG
);
227 data
[i
] = dmm32at_ai_get_sample(dev
, s
);
233 static int dmm32at_ai_check_chanlist(struct comedi_device
*dev
,
234 struct comedi_subdevice
*s
,
235 struct comedi_cmd
*cmd
)
237 unsigned int chan0
= CR_CHAN(cmd
->chanlist
[0]);
238 unsigned int range0
= CR_RANGE(cmd
->chanlist
[0]);
241 for (i
= 1; i
< cmd
->chanlist_len
; i
++) {
242 unsigned int chan
= CR_CHAN(cmd
->chanlist
[i
]);
243 unsigned int range
= CR_RANGE(cmd
->chanlist
[i
]);
245 if (chan
!= (chan0
+ i
) % s
->n_chan
) {
246 dev_dbg(dev
->class_dev
,
247 "entries in chanlist must be consecutive channels, counting upwards\n");
250 if (range
!= range0
) {
251 dev_dbg(dev
->class_dev
,
252 "entries in chanlist must all have the same gain\n");
260 static int dmm32at_ai_cmdtest(struct comedi_device
*dev
,
261 struct comedi_subdevice
*s
,
262 struct comedi_cmd
*cmd
)
267 /* Step 1 : check if triggers are trivially valid */
269 err
|= comedi_check_trigger_src(&cmd
->start_src
, TRIG_NOW
);
270 err
|= comedi_check_trigger_src(&cmd
->scan_begin_src
, TRIG_TIMER
);
271 err
|= comedi_check_trigger_src(&cmd
->convert_src
, TRIG_TIMER
);
272 err
|= comedi_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
273 err
|= comedi_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
278 /* Step 2a : make sure trigger sources are unique */
280 err
|= comedi_check_trigger_is_unique(cmd
->stop_src
);
282 /* Step 2b : and mutually compatible */
287 /* Step 3: check if arguments are trivially valid */
289 err
|= comedi_check_trigger_arg_is(&cmd
->start_arg
, 0);
291 err
|= comedi_check_trigger_arg_min(&cmd
->scan_begin_arg
, 1000000);
292 err
|= comedi_check_trigger_arg_max(&cmd
->scan_begin_arg
, 1000000000);
294 if (cmd
->convert_arg
>= 17500)
295 cmd
->convert_arg
= 20000;
296 else if (cmd
->convert_arg
>= 12500)
297 cmd
->convert_arg
= 15000;
298 else if (cmd
->convert_arg
>= 7500)
299 cmd
->convert_arg
= 10000;
301 cmd
->convert_arg
= 5000;
303 err
|= comedi_check_trigger_arg_is(&cmd
->scan_end_arg
,
306 if (cmd
->stop_src
== TRIG_COUNT
)
307 err
|= comedi_check_trigger_arg_min(&cmd
->stop_arg
, 1);
309 err
|= comedi_check_trigger_arg_is(&cmd
->stop_arg
, 0);
314 /* Step 4: fix up any arguments */
316 arg
= cmd
->convert_arg
* cmd
->scan_end_arg
;
317 err
|= comedi_check_trigger_arg_min(&cmd
->scan_begin_arg
, arg
);
322 /* Step 5: check channel list if it exists */
323 if (cmd
->chanlist
&& cmd
->chanlist_len
> 0)
324 err
|= dmm32at_ai_check_chanlist(dev
, s
, cmd
);
332 static void dmm32at_setaitimer(struct comedi_device
*dev
, unsigned int nansec
)
334 unsigned char lo1
, lo2
, hi2
;
335 unsigned short both2
;
337 /* based on 10mhz clock */
339 both2
= nansec
/ 20000;
340 hi2
= (both2
& 0xff00) >> 8;
341 lo2
= both2
& 0x00ff;
343 /* set counter clocks to 10MHz, disable all aux dio */
344 outb(0, dev
->iobase
+ DMM32AT_CTRDIO_CFG_REG
);
346 /* get access to the clock regs */
347 outb(DMM32AT_CTRL_PAGE_8254
, dev
->iobase
+ DMM32AT_CTRL_REG
);
349 /* write the counter 1 control word and low byte to counter */
350 outb(DMM32AT_CLKCT1
, dev
->iobase
+ DMM32AT_CLKCT
);
351 outb(lo1
, dev
->iobase
+ DMM32AT_CLK1
);
353 /* write the counter 2 control word and low byte then to counter */
354 outb(DMM32AT_CLKCT2
, dev
->iobase
+ DMM32AT_CLKCT
);
355 outb(lo2
, dev
->iobase
+ DMM32AT_CLK2
);
356 outb(hi2
, dev
->iobase
+ DMM32AT_CLK2
);
358 /* enable the ai conversion interrupt and the clock to start scans */
359 outb(DMM32AT_INTCLK_ADINT
|
360 DMM32AT_INTCLK_CLKEN
| DMM32AT_INTCLK_CLKSEL
,
361 dev
->iobase
+ DMM32AT_INTCLK_REG
);
364 static int dmm32at_ai_cmd(struct comedi_device
*dev
, struct comedi_subdevice
*s
)
366 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
369 dmm32at_ai_set_chanspec(dev
, s
, cmd
->chanlist
[0], cmd
->chanlist_len
);
371 /* reset the interrupt just in case */
372 outb(DMM32AT_CTRL_INTRST
, dev
->iobase
+ DMM32AT_CTRL_REG
);
375 * wait for circuit to settle
376 * we don't have the 'insn' here but it's not needed
378 ret
= comedi_timeout(dev
, s
, NULL
, dmm32at_ai_status
,
379 DMM32AT_AI_READBACK_REG
);
383 if (cmd
->stop_src
== TRIG_NONE
|| cmd
->stop_arg
> 1) {
384 /* start the clock and enable the interrupts */
385 dmm32at_setaitimer(dev
, cmd
->scan_begin_arg
);
387 /* start the interrupts and initiate a single scan */
388 outb(DMM32AT_INTCLK_ADINT
, dev
->iobase
+ DMM32AT_INTCLK_REG
);
389 outb(0xff, dev
->iobase
+ DMM32AT_AI_START_CONV_REG
);
395 static int dmm32at_ai_cancel(struct comedi_device
*dev
,
396 struct comedi_subdevice
*s
)
398 /* disable further interrupts and clocks */
399 outb(0x0, dev
->iobase
+ DMM32AT_INTCLK_REG
);
403 static irqreturn_t
dmm32at_isr(int irq
, void *d
)
405 struct comedi_device
*dev
= d
;
406 unsigned char intstat
;
410 if (!dev
->attached
) {
411 dev_err(dev
->class_dev
, "spurious interrupt\n");
415 intstat
= inb(dev
->iobase
+ DMM32AT_INTCLK_REG
);
417 if (intstat
& DMM32AT_INTCLK_ADINT
) {
418 struct comedi_subdevice
*s
= dev
->read_subdev
;
419 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
421 for (i
= 0; i
< cmd
->chanlist_len
; i
++) {
422 val
= dmm32at_ai_get_sample(dev
, s
);
423 comedi_buf_write_samples(s
, &val
, 1);
426 if (cmd
->stop_src
== TRIG_COUNT
&&
427 s
->async
->scans_done
>= cmd
->stop_arg
)
428 s
->async
->events
|= COMEDI_CB_EOA
;
430 comedi_handle_events(dev
, s
);
433 /* reset the interrupt */
434 outb(DMM32AT_CTRL_INTRST
, dev
->iobase
+ DMM32AT_CTRL_REG
);
438 static int dmm32at_ao_eoc(struct comedi_device
*dev
,
439 struct comedi_subdevice
*s
,
440 struct comedi_insn
*insn
,
441 unsigned long context
)
443 unsigned char status
;
445 status
= inb(dev
->iobase
+ DMM32AT_AUX_DI_REG
);
446 if ((status
& DMM32AT_AUX_DI_DACBUSY
) == 0)
451 static int dmm32at_ao_insn_write(struct comedi_device
*dev
,
452 struct comedi_subdevice
*s
,
453 struct comedi_insn
*insn
,
456 unsigned int chan
= CR_CHAN(insn
->chanspec
);
459 for (i
= 0; i
< insn
->n
; i
++) {
460 unsigned int val
= data
[i
];
463 /* write LSB then MSB + chan to load DAC */
464 outb(val
& 0xff, dev
->iobase
+ DMM32AT_AO_LSB_REG
);
465 outb((val
>> 8) | DMM32AT_AO_MSB_DACH(chan
),
466 dev
->iobase
+ DMM32AT_AO_MSB_REG
);
468 /* wait for circuit to settle */
469 ret
= comedi_timeout(dev
, s
, insn
, dmm32at_ao_eoc
, 0);
473 /* dummy read to update DAC */
474 inb(dev
->iobase
+ DMM32AT_AO_MSB_REG
);
476 s
->readback
[chan
] = val
;
482 static int dmm32at_8255_io(struct comedi_device
*dev
,
483 int dir
, int port
, int data
, unsigned long regbase
)
485 /* get access to the DIO regs */
486 outb(DMM32AT_CTRL_PAGE_8255
, dev
->iobase
+ DMM32AT_CTRL_REG
);
489 outb(data
, dev
->iobase
+ regbase
+ port
);
492 return inb(dev
->iobase
+ regbase
+ port
);
495 /* Make sure the board is there and put it to a known state */
496 static int dmm32at_reset(struct comedi_device
*dev
)
498 unsigned char aihi
, ailo
, fifostat
, aistat
, intstat
, airback
;
500 /* reset the board */
501 outb(DMM32AT_CTRL_RESETA
, dev
->iobase
+ DMM32AT_CTRL_REG
);
503 /* allow a millisecond to reset */
504 usleep_range(1000, 3000);
506 /* zero scan and fifo control */
507 outb(0x0, dev
->iobase
+ DMM32AT_FIFO_CTRL_REG
);
509 /* zero interrupt and clock control */
510 outb(0x0, dev
->iobase
+ DMM32AT_INTCLK_REG
);
512 /* write a test channel range, the high 3 bits should drop */
513 outb(0x80, dev
->iobase
+ DMM32AT_AI_LO_CHAN_REG
);
514 outb(0xff, dev
->iobase
+ DMM32AT_AI_HI_CHAN_REG
);
516 /* set the range at 10v unipolar */
517 outb(DMM32AT_RANGE_U10
, dev
->iobase
+ DMM32AT_AI_CFG_REG
);
519 /* should take 10 us to settle, here's a hundred */
520 usleep_range(100, 200);
522 /* read back the values */
523 ailo
= inb(dev
->iobase
+ DMM32AT_AI_LO_CHAN_REG
);
524 aihi
= inb(dev
->iobase
+ DMM32AT_AI_HI_CHAN_REG
);
525 fifostat
= inb(dev
->iobase
+ DMM32AT_FIFO_STATUS_REG
);
526 aistat
= inb(dev
->iobase
+ DMM32AT_AI_STATUS_REG
);
527 intstat
= inb(dev
->iobase
+ DMM32AT_INTCLK_REG
);
528 airback
= inb(dev
->iobase
+ DMM32AT_AI_READBACK_REG
);
531 * NOTE: The (DMM32AT_AI_STATUS_SD1 | DMM32AT_AI_STATUS_SD0)
532 * test makes this driver only work if the board is configured
533 * with all A/D channels set for single-ended operation.
535 if (ailo
!= 0x00 || aihi
!= 0x1f ||
536 fifostat
!= DMM32AT_FIFO_STATUS_EF
||
537 aistat
!= (DMM32AT_AI_STATUS_SD1
| DMM32AT_AI_STATUS_SD0
) ||
538 intstat
!= 0x00 || airback
!= 0x0c)
544 static int dmm32at_attach(struct comedi_device
*dev
,
545 struct comedi_devconfig
*it
)
547 struct comedi_subdevice
*s
;
550 ret
= comedi_request_region(dev
, it
->options
[0], 0x10);
554 ret
= dmm32at_reset(dev
);
556 dev_err(dev
->class_dev
, "board detection failed\n");
560 if (it
->options
[1]) {
561 ret
= request_irq(it
->options
[1], dmm32at_isr
, 0,
562 dev
->board_name
, dev
);
564 dev
->irq
= it
->options
[1];
567 ret
= comedi_alloc_subdevices(dev
, 3);
571 /* Analog Input subdevice */
572 s
= &dev
->subdevices
[0];
573 s
->type
= COMEDI_SUBD_AI
;
574 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_DIFF
;
577 s
->range_table
= &dmm32at_airanges
;
578 s
->insn_read
= dmm32at_ai_insn_read
;
580 dev
->read_subdev
= s
;
581 s
->subdev_flags
|= SDF_CMD_READ
;
582 s
->len_chanlist
= s
->n_chan
;
583 s
->do_cmd
= dmm32at_ai_cmd
;
584 s
->do_cmdtest
= dmm32at_ai_cmdtest
;
585 s
->cancel
= dmm32at_ai_cancel
;
588 /* Analog Output subdevice */
589 s
= &dev
->subdevices
[1];
590 s
->type
= COMEDI_SUBD_AO
;
591 s
->subdev_flags
= SDF_WRITABLE
;
594 s
->range_table
= &dmm32at_aoranges
;
595 s
->insn_write
= dmm32at_ao_insn_write
;
597 ret
= comedi_alloc_subdev_readback(s
);
601 /* Digital I/O subdevice */
602 s
= &dev
->subdevices
[2];
603 return subdev_8255_init(dev
, s
, dmm32at_8255_io
, DMM32AT_8255_IOBASE
);
606 static struct comedi_driver dmm32at_driver
= {
607 .driver_name
= "dmm32at",
608 .module
= THIS_MODULE
,
609 .attach
= dmm32at_attach
,
610 .detach
= comedi_legacy_detach
,
612 module_comedi_driver(dmm32at_driver
);
614 MODULE_AUTHOR("Comedi http://www.comedi.org");
615 MODULE_DESCRIPTION("Comedi: Diamond Systems Diamond-MM-32-AT");
616 MODULE_LICENSE("GPL");