1/*
2   comedi/drivers/daqboard2000.c
3   hardware driver for IOtech DAQboard/2000
4
5   COMEDI - Linux Control and Measurement Device Interface
6   Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
7
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17 */
18/*
19Driver: daqboard2000
20Description: IOTech DAQBoard/2000
21Author: Anders Blomdell <anders.blomdell@control.lth.se>
22Status: works
23Updated: Mon, 14 Apr 2008 15:28:52 +0100
24Devices: [IOTech] DAQBoard/2000 (daqboard2000)
25
26Much of the functionality of this driver was determined from reading
27the source code for the Windows driver.
28
29The FPGA on the board requires fimware, which is available from
30http://www.comedi.org in the comedi_nonfree_firmware tarball.
31
32Configuration options: not applicable, uses PCI auto config
33*/
34/*
35   This card was obviously never intended to leave the Windows world,
36   since it lacked all kind of hardware documentation (except for cable
37   pinouts, plug and pray has something to catch up with yet).
38
39   With some help from our swedish distributor, we got the Windows sourcecode
40   for the card, and here are the findings so far.
41
42   1. A good document that describes the PCI interface chip is 9080db-106.pdf
43      available from http://www.plxtech.com/products/io/pci9080
44
45   2. The initialization done so far is:
46        a. program the FPGA (windows code sans a lot of error messages)
47	b.
48
49   3. Analog out seems to work OK with DAC's disabled, if DAC's are enabled,
50      you have to output values to all enabled DAC's until result appears, I
51      guess that it has something to do with pacer clocks, but the source
52      gives me no clues. I'll keep it simple so far.
53
54   4. Analog in.
55        Each channel in the scanlist seems to be controlled by four
56	control words:
57
58        Word0:
59          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
60          ! | | | ! | | | ! | | | ! | | | !
61          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62
63        Word1:
64          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
65          ! | | | ! | | | ! | | | ! | | | !
66          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
67	   |             |       | | | | |
68           +------+------+       | | | | +-- Digital input (??)
69		  |		 | | | +---- 10 us settling time
70		  |		 | | +------ Suspend acquisition (last to scan)
71		  |		 | +-------- Simultaneous sample and hold
72		  |		 +---------- Signed data format
73		  +------------------------- Correction offset low
74
75        Word2:
76          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
77          ! | | | ! | | | ! | | | ! | | | !
78          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79           |     | |     | | | | | |     |
80           +-----+ +--+--+ +++ +++ +--+--+
81              |       |     |   |     +----- Expansion channel
82	      |       |     |   +----------- Expansion gain
83              |       |     +--------------- Channel (low)
84	      |       +--------------------- Correction offset high
85	      +----------------------------- Correction gain low
86        Word3:
87          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
88          ! | | | ! | | | ! | | | ! | | | !
89          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
90           |             | | | |   | | | |
91           +------+------+ | | +-+-+ | | +-- Low bank enable
92                  |        | |   |   | +---- High bank enable
93                  |        | |   |   +------ Hi/low select
94		  |    	   | |   +---------- Gain (1,?,2,4,8,16,32,64)
95		  |    	   | +-------------- differential/single ended
96		  |    	   +---------------- Unipolar
97		  +------------------------- Correction gain high
98
99   999. The card seems to have an incredible amount of capabilities, but
100        trying to reverse engineer them from the Windows source is beyond my
101	patience.
102
103 */
104
105#include <linux/module.h>
106#include <linux/delay.h>
107#include <linux/interrupt.h>
108
109#include "../comedi_pci.h"
110
111#include "8255.h"
112
113#define DAQBOARD2000_FIRMWARE		"daqboard2000_firmware.bin"
114
115#define DAQBOARD2000_SUBSYSTEM_IDS2	0x0002	/* Daqboard/2000 - 2 Dacs */
116#define DAQBOARD2000_SUBSYSTEM_IDS4	0x0004	/* Daqboard/2000 - 4 Dacs */
117
118/* Initialization bits for the Serial EEPROM Control Register */
119#define DAQBOARD2000_SECRProgPinHi      0x8001767e
120#define DAQBOARD2000_SECRProgPinLo      0x8000767e
121#define DAQBOARD2000_SECRLocalBusHi     0xc000767e
122#define DAQBOARD2000_SECRLocalBusLo     0x8000767e
123#define DAQBOARD2000_SECRReloadHi       0xa000767e
124#define DAQBOARD2000_SECRReloadLo       0x8000767e
125
126/* SECR status bits */
127#define DAQBOARD2000_EEPROM_PRESENT     0x10000000
128
129/* CPLD status bits */
130#define DAQBOARD2000_CPLD_INIT		0x0002
131#define DAQBOARD2000_CPLD_DONE		0x0004
132
133static const struct comedi_lrange range_daqboard2000_ai = {
134	13, {
135		BIP_RANGE(10),
136		BIP_RANGE(5),
137		BIP_RANGE(2.5),
138		BIP_RANGE(1.25),
139		BIP_RANGE(0.625),
140		BIP_RANGE(0.3125),
141		BIP_RANGE(0.156),
142		UNI_RANGE(10),
143		UNI_RANGE(5),
144		UNI_RANGE(2.5),
145		UNI_RANGE(1.25),
146		UNI_RANGE(0.625),
147		UNI_RANGE(0.3125)
148	}
149};
150
151/*
152 * Register Memory Map
153 */
154#define acqControl			0x00		/* u16 */
155#define acqScanListFIFO			0x02		/* u16 */
156#define acqPacerClockDivLow		0x04		/* u32 */
157#define acqScanCounter			0x08		/* u16 */
158#define acqPacerClockDivHigh		0x0a		/* u16 */
159#define acqTriggerCount			0x0c		/* u16 */
160#define acqResultsFIFO			0x10		/* u16 */
161#define acqResultsShadow		0x14		/* u16 */
162#define acqAdcResult			0x18		/* u16 */
163#define dacScanCounter			0x1c		/* u16 */
164#define dacControl			0x20		/* u16 */
165#define dacFIFO				0x24		/* s16 */
166#define dacPacerClockDiv		0x2a		/* u16 */
167#define refDacs				0x2c		/* u16 */
168#define dioControl			0x30		/* u16 */
169#define dioP3hsioData			0x32		/* s16 */
170#define dioP3Control			0x34		/* u16 */
171#define calEepromControl		0x36		/* u16 */
172#define dacSetting(x)			(0x38 + (x)*2)	/* s16 */
173#define dioP2ExpansionIO8Bit		0x40		/* s16 */
174#define ctrTmrControl			0x80		/* u16 */
175#define ctrInput(x)			(0x88 + (x)*2)	/* s16 */
176#define timerDivisor(x)			(0xa0 + (x)*2)	/* u16 */
177#define dmaControl			0xb0		/* u16 */
178#define trigControl			0xb2		/* u16 */
179#define calEeprom			0xb8		/* u16 */
180#define acqDigitalMark			0xba		/* u16 */
181#define trigDacs			0xbc		/* u16 */
182#define dioP2ExpansionIO16Bit(x)	(0xc0 + (x)*2)	/* s16 */
183
184/* Scan Sequencer programming */
185#define DAQBOARD2000_SeqStartScanList            0x0011
186#define DAQBOARD2000_SeqStopScanList             0x0010
187
188/* Prepare for acquisition */
189#define DAQBOARD2000_AcqResetScanListFifo        0x0004
190#define DAQBOARD2000_AcqResetResultsFifo         0x0002
191#define DAQBOARD2000_AcqResetConfigPipe          0x0001
192
193/* Acqusition status bits */
194#define DAQBOARD2000_AcqResultsFIFOMore1Sample   0x0001
195#define DAQBOARD2000_AcqResultsFIFOHasValidData  0x0002
196#define DAQBOARD2000_AcqResultsFIFOOverrun       0x0004
197#define DAQBOARD2000_AcqLogicScanning            0x0008
198#define DAQBOARD2000_AcqConfigPipeFull           0x0010
199#define DAQBOARD2000_AcqScanListFIFOEmpty        0x0020
200#define DAQBOARD2000_AcqAdcNotReady              0x0040
201#define DAQBOARD2000_ArbitrationFailure          0x0080
202#define DAQBOARD2000_AcqPacerOverrun             0x0100
203#define DAQBOARD2000_DacPacerOverrun             0x0200
204#define DAQBOARD2000_AcqHardwareError            0x01c0
205
206/* Scan Sequencer programming */
207#define DAQBOARD2000_SeqStartScanList            0x0011
208#define DAQBOARD2000_SeqStopScanList             0x0010
209
210/* Pacer Clock Control */
211#define DAQBOARD2000_AdcPacerInternal            0x0030
212#define DAQBOARD2000_AdcPacerExternal            0x0032
213#define DAQBOARD2000_AdcPacerEnable              0x0031
214#define DAQBOARD2000_AdcPacerEnableDacPacer      0x0034
215#define DAQBOARD2000_AdcPacerDisable             0x0030
216#define DAQBOARD2000_AdcPacerNormalMode          0x0060
217#define DAQBOARD2000_AdcPacerCompatibilityMode   0x0061
218#define DAQBOARD2000_AdcPacerInternalOutEnable   0x0008
219#define DAQBOARD2000_AdcPacerExternalRising      0x0100
220
221/* DAC status */
222#define DAQBOARD2000_DacFull                     0x0001
223#define DAQBOARD2000_RefBusy                     0x0002
224#define DAQBOARD2000_TrgBusy                     0x0004
225#define DAQBOARD2000_CalBusy                     0x0008
226#define DAQBOARD2000_Dac0Busy                    0x0010
227#define DAQBOARD2000_Dac1Busy                    0x0020
228#define DAQBOARD2000_Dac2Busy                    0x0040
229#define DAQBOARD2000_Dac3Busy                    0x0080
230
231/* DAC control */
232#define DAQBOARD2000_Dac0Enable                  0x0021
233#define DAQBOARD2000_Dac1Enable                  0x0031
234#define DAQBOARD2000_Dac2Enable                  0x0041
235#define DAQBOARD2000_Dac3Enable                  0x0051
236#define DAQBOARD2000_DacEnableBit                0x0001
237#define DAQBOARD2000_Dac0Disable                 0x0020
238#define DAQBOARD2000_Dac1Disable                 0x0030
239#define DAQBOARD2000_Dac2Disable                 0x0040
240#define DAQBOARD2000_Dac3Disable                 0x0050
241#define DAQBOARD2000_DacResetFifo                0x0004
242#define DAQBOARD2000_DacPatternDisable           0x0060
243#define DAQBOARD2000_DacPatternEnable            0x0061
244#define DAQBOARD2000_DacSelectSignedData         0x0002
245#define DAQBOARD2000_DacSelectUnsignedData       0x0000
246
247/* Trigger Control */
248#define DAQBOARD2000_TrigAnalog                  0x0000
249#define DAQBOARD2000_TrigTTL                     0x0010
250#define DAQBOARD2000_TrigTransHiLo               0x0004
251#define DAQBOARD2000_TrigTransLoHi               0x0000
252#define DAQBOARD2000_TrigAbove                   0x0000
253#define DAQBOARD2000_TrigBelow                   0x0004
254#define DAQBOARD2000_TrigLevelSense              0x0002
255#define DAQBOARD2000_TrigEdgeSense               0x0000
256#define DAQBOARD2000_TrigEnable                  0x0001
257#define DAQBOARD2000_TrigDisable                 0x0000
258
259/* Reference Dac Selection */
260#define DAQBOARD2000_PosRefDacSelect             0x0100
261#define DAQBOARD2000_NegRefDacSelect             0x0000
262
263struct daq200_boardtype {
264	const char *name;
265	int id;
266};
267static const struct daq200_boardtype boardtypes[] = {
268	{"ids2", DAQBOARD2000_SUBSYSTEM_IDS2},
269	{"ids4", DAQBOARD2000_SUBSYSTEM_IDS4},
270};
271
272struct daqboard2000_private {
273	enum {
274		card_daqboard_2000
275	} card;
276	void __iomem *plx;
277};
278
279static void writeAcqScanListEntry(struct comedi_device *dev, u16 entry)
280{
281	/* udelay(4); */
282	writew(entry & 0x00ff, dev->mmio + acqScanListFIFO);
283	/* udelay(4); */
284	writew((entry >> 8) & 0x00ff, dev->mmio + acqScanListFIFO);
285}
286
287static void setup_sampling(struct comedi_device *dev, int chan, int gain)
288{
289	u16 word0, word1, word2, word3;
290
291	/* Channel 0-7 diff, channel 8-23 single ended */
292	word0 = 0;
293	word1 = 0x0004;		/* Last scan */
294	word2 = (chan << 6) & 0x00c0;
295	switch (chan / 4) {
296	case 0:
297		word3 = 0x0001;
298		break;
299	case 1:
300		word3 = 0x0002;
301		break;
302	case 2:
303		word3 = 0x0005;
304		break;
305	case 3:
306		word3 = 0x0006;
307		break;
308	case 4:
309		word3 = 0x0041;
310		break;
311	case 5:
312		word3 = 0x0042;
313		break;
314	default:
315		word3 = 0;
316		break;
317	}
318/*
319  dev->eeprom.correctionDACSE[i][j][k].offset = 0x800;
320  dev->eeprom.correctionDACSE[i][j][k].gain = 0xc00;
321*/
322	/* These should be read from EEPROM */
323	word2 |= 0x0800;
324	word3 |= 0xc000;
325	writeAcqScanListEntry(dev, word0);
326	writeAcqScanListEntry(dev, word1);
327	writeAcqScanListEntry(dev, word2);
328	writeAcqScanListEntry(dev, word3);
329}
330
331static int daqboard2000_ai_status(struct comedi_device *dev,
332				  struct comedi_subdevice *s,
333				  struct comedi_insn *insn,
334				  unsigned long context)
335{
336	unsigned int status;
337
338	status = readw(dev->mmio + acqControl);
339	if (status & context)
340		return 0;
341	return -EBUSY;
342}
343
344static int daqboard2000_ai_insn_read(struct comedi_device *dev,
345				     struct comedi_subdevice *s,
346				     struct comedi_insn *insn,
347				     unsigned int *data)
348{
349	int gain, chan;
350	int ret;
351	int i;
352
353	writew(DAQBOARD2000_AcqResetScanListFifo |
354	       DAQBOARD2000_AcqResetResultsFifo |
355	       DAQBOARD2000_AcqResetConfigPipe, dev->mmio + acqControl);
356
357	/*
358	 * If pacer clock is not set to some high value (> 10 us), we
359	 * risk multiple samples to be put into the result FIFO.
360	 */
361	/* 1 second, should be long enough */
362	writel(1000000, dev->mmio + acqPacerClockDivLow);
363	writew(0, dev->mmio + acqPacerClockDivHigh);
364
365	gain = CR_RANGE(insn->chanspec);
366	chan = CR_CHAN(insn->chanspec);
367
368	/* This doesn't look efficient.  I decided to take the conservative
369	 * approach when I did the insn conversion.  Perhaps it would be
370	 * better to have broken it completely, then someone would have been
371	 * forced to fix it.  --ds */
372	for (i = 0; i < insn->n; i++) {
373		setup_sampling(dev, chan, gain);
374		/* Enable reading from the scanlist FIFO */
375		writew(DAQBOARD2000_SeqStartScanList, dev->mmio + acqControl);
376
377		ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
378				     DAQBOARD2000_AcqConfigPipeFull);
379		if (ret)
380			return ret;
381
382		writew(DAQBOARD2000_AdcPacerEnable, dev->mmio + acqControl);
383
384		ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
385				     DAQBOARD2000_AcqLogicScanning);
386		if (ret)
387			return ret;
388
389		ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
390				     DAQBOARD2000_AcqResultsFIFOHasValidData);
391		if (ret)
392			return ret;
393
394		data[i] = readw(dev->mmio + acqResultsFIFO);
395		writew(DAQBOARD2000_AdcPacerDisable, dev->mmio + acqControl);
396		writew(DAQBOARD2000_SeqStopScanList, dev->mmio + acqControl);
397	}
398
399	return i;
400}
401
402static int daqboard2000_ao_eoc(struct comedi_device *dev,
403			       struct comedi_subdevice *s,
404			       struct comedi_insn *insn,
405			       unsigned long context)
406{
407	unsigned int chan = CR_CHAN(insn->chanspec);
408	unsigned int status;
409
410	status = readw(dev->mmio + dacControl);
411	if ((status & ((chan + 1) * 0x0010)) == 0)
412		return 0;
413	return -EBUSY;
414}
415
416static int daqboard2000_ao_insn_write(struct comedi_device *dev,
417				      struct comedi_subdevice *s,
418				      struct comedi_insn *insn,
419				      unsigned int *data)
420{
421	unsigned int chan = CR_CHAN(insn->chanspec);
422	int i;
423
424	for (i = 0; i < insn->n; i++) {
425		unsigned int val = data[i];
426		int ret;
427
428		writew(val, dev->mmio + dacSetting(chan));
429
430		ret = comedi_timeout(dev, s, insn, daqboard2000_ao_eoc, 0);
431		if (ret)
432			return ret;
433
434		s->readback[chan] = val;
435	}
436
437	return insn->n;
438}
439
440static void daqboard2000_resetLocalBus(struct comedi_device *dev)
441{
442	struct daqboard2000_private *devpriv = dev->private;
443
444	writel(DAQBOARD2000_SECRLocalBusHi, devpriv->plx + 0x6c);
445	mdelay(10);
446	writel(DAQBOARD2000_SECRLocalBusLo, devpriv->plx + 0x6c);
447	mdelay(10);
448}
449
450static void daqboard2000_reloadPLX(struct comedi_device *dev)
451{
452	struct daqboard2000_private *devpriv = dev->private;
453
454	writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c);
455	mdelay(10);
456	writel(DAQBOARD2000_SECRReloadHi, devpriv->plx + 0x6c);
457	mdelay(10);
458	writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c);
459	mdelay(10);
460}
461
462static void daqboard2000_pulseProgPin(struct comedi_device *dev)
463{
464	struct daqboard2000_private *devpriv = dev->private;
465
466	writel(DAQBOARD2000_SECRProgPinHi, devpriv->plx + 0x6c);
467	mdelay(10);
468	writel(DAQBOARD2000_SECRProgPinLo, devpriv->plx + 0x6c);
469	mdelay(10);	/* Not in the original code, but I like symmetry... */
470}
471
472static int daqboard2000_pollCPLD(struct comedi_device *dev, int mask)
473{
474	int result = 0;
475	int i;
476	int cpld;
477
478	/* timeout after 50 tries -> 5ms */
479	for (i = 0; i < 50; i++) {
480		cpld = readw(dev->mmio + 0x1000);
481		if ((cpld & mask) == mask) {
482			result = 1;
483			break;
484		}
485		udelay(100);
486	}
487	udelay(5);
488	return result;
489}
490
491static int daqboard2000_writeCPLD(struct comedi_device *dev, int data)
492{
493	int result = 0;
494
495	udelay(10);
496	writew(data, dev->mmio + 0x1000);
497	if ((readw(dev->mmio + 0x1000) & DAQBOARD2000_CPLD_INIT) ==
498	    DAQBOARD2000_CPLD_INIT) {
499		result = 1;
500	}
501	return result;
502}
503
504static int initialize_daqboard2000(struct comedi_device *dev,
505				   const u8 *cpld_array, size_t len,
506				   unsigned long context)
507{
508	struct daqboard2000_private *devpriv = dev->private;
509	int result = -EIO;
510	/* Read the serial EEPROM control register */
511	int secr;
512	int retry;
513	size_t i;
514
515	/* Check to make sure the serial eeprom is present on the board */
516	secr = readl(devpriv->plx + 0x6c);
517	if (!(secr & DAQBOARD2000_EEPROM_PRESENT))
518		return -EIO;
519
520	for (retry = 0; retry < 3; retry++) {
521		daqboard2000_resetLocalBus(dev);
522		daqboard2000_reloadPLX(dev);
523		daqboard2000_pulseProgPin(dev);
524		if (daqboard2000_pollCPLD(dev, DAQBOARD2000_CPLD_INIT)) {
525			for (i = 0; i < len; i++) {
526				if (cpld_array[i] == 0xff &&
527				    cpld_array[i + 1] == 0x20)
528					break;
529			}
530			for (; i < len; i += 2) {
531				int data =
532				    (cpld_array[i] << 8) + cpld_array[i + 1];
533				if (!daqboard2000_writeCPLD(dev, data))
534					break;
535			}
536			if (i >= len) {
537				daqboard2000_resetLocalBus(dev);
538				daqboard2000_reloadPLX(dev);
539				result = 0;
540				break;
541			}
542		}
543	}
544	return result;
545}
546
547static void daqboard2000_adcStopDmaTransfer(struct comedi_device *dev)
548{
549}
550
551static void daqboard2000_adcDisarm(struct comedi_device *dev)
552{
553	/* Disable hardware triggers */
554	udelay(2);
555	writew(DAQBOARD2000_TrigAnalog | DAQBOARD2000_TrigDisable,
556	       dev->mmio + trigControl);
557	udelay(2);
558	writew(DAQBOARD2000_TrigTTL | DAQBOARD2000_TrigDisable,
559	       dev->mmio + trigControl);
560
561	/* Stop the scan list FIFO from loading the configuration pipe */
562	udelay(2);
563	writew(DAQBOARD2000_SeqStopScanList, dev->mmio + acqControl);
564
565	/* Stop the pacer clock */
566	udelay(2);
567	writew(DAQBOARD2000_AdcPacerDisable, dev->mmio + acqControl);
568
569	/* Stop the input dma (abort channel 1) */
570	daqboard2000_adcStopDmaTransfer(dev);
571}
572
573static void daqboard2000_activateReferenceDacs(struct comedi_device *dev)
574{
575	unsigned int val;
576	int timeout;
577
578	/*  Set the + reference dac value in the FPGA */
579	writew(0x80 | DAQBOARD2000_PosRefDacSelect, dev->mmio + refDacs);
580	for (timeout = 0; timeout < 20; timeout++) {
581		val = readw(dev->mmio + dacControl);
582		if ((val & DAQBOARD2000_RefBusy) == 0)
583			break;
584		udelay(2);
585	}
586
587	/*  Set the - reference dac value in the FPGA */
588	writew(0x80 | DAQBOARD2000_NegRefDacSelect, dev->mmio + refDacs);
589	for (timeout = 0; timeout < 20; timeout++) {
590		val = readw(dev->mmio + dacControl);
591		if ((val & DAQBOARD2000_RefBusy) == 0)
592			break;
593		udelay(2);
594	}
595}
596
597static void daqboard2000_initializeCtrs(struct comedi_device *dev)
598{
599}
600
601static void daqboard2000_initializeTmrs(struct comedi_device *dev)
602{
603}
604
605static void daqboard2000_dacDisarm(struct comedi_device *dev)
606{
607}
608
609static void daqboard2000_initializeAdc(struct comedi_device *dev)
610{
611	daqboard2000_adcDisarm(dev);
612	daqboard2000_activateReferenceDacs(dev);
613	daqboard2000_initializeCtrs(dev);
614	daqboard2000_initializeTmrs(dev);
615}
616
617static void daqboard2000_initializeDac(struct comedi_device *dev)
618{
619	daqboard2000_dacDisarm(dev);
620}
621
622static int daqboard2000_8255_cb(struct comedi_device *dev,
623				int dir, int port, int data,
624				unsigned long iobase)
625{
626	if (dir) {
627		writew(data, dev->mmio + iobase + port * 2);
628		return 0;
629	}
630	return readw(dev->mmio + iobase + port * 2);
631}
632
633static const void *daqboard2000_find_boardinfo(struct comedi_device *dev,
634					       struct pci_dev *pcidev)
635{
636	const struct daq200_boardtype *board;
637	int i;
638
639	if (pcidev->subsystem_device != PCI_VENDOR_ID_IOTECH)
640		return NULL;
641
642	for (i = 0; i < ARRAY_SIZE(boardtypes); i++) {
643		board = &boardtypes[i];
644		if (pcidev->subsystem_device == board->id)
645			return board;
646	}
647	return NULL;
648}
649
650static int daqboard2000_auto_attach(struct comedi_device *dev,
651				    unsigned long context_unused)
652{
653	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
654	const struct daq200_boardtype *board;
655	struct daqboard2000_private *devpriv;
656	struct comedi_subdevice *s;
657	int result;
658
659	board = daqboard2000_find_boardinfo(dev, pcidev);
660	if (!board)
661		return -ENODEV;
662	dev->board_ptr = board;
663	dev->board_name = board->name;
664
665	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
666	if (!devpriv)
667		return -ENOMEM;
668
669	result = comedi_pci_enable(dev);
670	if (result)
671		return result;
672
673	devpriv->plx = pci_ioremap_bar(pcidev, 0);
674	dev->mmio = pci_ioremap_bar(pcidev, 2);
675	if (!devpriv->plx || !dev->mmio)
676		return -ENOMEM;
677
678	result = comedi_alloc_subdevices(dev, 3);
679	if (result)
680		return result;
681
682	readl(devpriv->plx + 0x6c);
683
684	result = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
685				      DAQBOARD2000_FIRMWARE,
686				      initialize_daqboard2000, 0);
687	if (result < 0)
688		return result;
689
690	daqboard2000_initializeAdc(dev);
691	daqboard2000_initializeDac(dev);
692
693	s = &dev->subdevices[0];
694	/* ai subdevice */
695	s->type = COMEDI_SUBD_AI;
696	s->subdev_flags = SDF_READABLE | SDF_GROUND;
697	s->n_chan = 24;
698	s->maxdata = 0xffff;
699	s->insn_read = daqboard2000_ai_insn_read;
700	s->range_table = &range_daqboard2000_ai;
701
702	s = &dev->subdevices[1];
703	/* ao subdevice */
704	s->type = COMEDI_SUBD_AO;
705	s->subdev_flags = SDF_WRITABLE;
706	s->n_chan = 2;
707	s->maxdata = 0xffff;
708	s->insn_write = daqboard2000_ao_insn_write;
709	s->range_table = &range_bipolar10;
710
711	result = comedi_alloc_subdev_readback(s);
712	if (result)
713		return result;
714
715	s = &dev->subdevices[2];
716	result = subdev_8255_init(dev, s, daqboard2000_8255_cb,
717				  dioP2ExpansionIO8Bit);
718	if (result)
719		return result;
720
721	return 0;
722}
723
724static void daqboard2000_detach(struct comedi_device *dev)
725{
726	struct daqboard2000_private *devpriv = dev->private;
727
728	if (devpriv && devpriv->plx)
729		iounmap(devpriv->plx);
730	comedi_pci_detach(dev);
731}
732
733static struct comedi_driver daqboard2000_driver = {
734	.driver_name	= "daqboard2000",
735	.module		= THIS_MODULE,
736	.auto_attach	= daqboard2000_auto_attach,
737	.detach		= daqboard2000_detach,
738};
739
740static int daqboard2000_pci_probe(struct pci_dev *dev,
741				  const struct pci_device_id *id)
742{
743	return comedi_pci_auto_config(dev, &daqboard2000_driver,
744				      id->driver_data);
745}
746
747static const struct pci_device_id daqboard2000_pci_table[] = {
748	{ PCI_DEVICE(PCI_VENDOR_ID_IOTECH, 0x0409) },
749	{ 0 }
750};
751MODULE_DEVICE_TABLE(pci, daqboard2000_pci_table);
752
753static struct pci_driver daqboard2000_pci_driver = {
754	.name		= "daqboard2000",
755	.id_table	= daqboard2000_pci_table,
756	.probe		= daqboard2000_pci_probe,
757	.remove		= comedi_pci_auto_unconfig,
758};
759module_comedi_pci_driver(daqboard2000_driver, daqboard2000_pci_driver);
760
761MODULE_AUTHOR("Comedi http://www.comedi.org");
762MODULE_DESCRIPTION("Comedi low-level driver");
763MODULE_LICENSE("GPL");
764MODULE_FIRMWARE(DAQBOARD2000_FIRMWARE);
765