1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Comedi driver for National Instruments AT-MIO16D board
4 * Copyright (C) 2000 Chris R. Baugher <baugher@enteract.com>
5 */
6
7/*
8 * Driver: ni_atmio16d
9 * Description: National Instruments AT-MIO-16D
10 * Author: Chris R. Baugher <baugher@enteract.com>
11 * Status: unknown
12 * Devices: [National Instruments] AT-MIO-16 (atmio16), AT-MIO-16D (atmio16d)
13 *
14 * Configuration options:
15 *   [0] - I/O port
16 *   [1] - MIO irq (0 == no irq; or 3,4,5,6,7,9,10,11,12,14,15)
17 *   [2] - DIO irq (0 == no irq; or 3,4,5,6,7,9)
18 *   [3] - DMA1 channel (0 == no DMA; or 5,6,7)
19 *   [4] - DMA2 channel (0 == no DMA; or 5,6,7)
20 *   [5] - a/d mux (0=differential; 1=single)
21 *   [6] - a/d range (0=bipolar10; 1=bipolar5; 2=unipolar10)
22 *   [7] - dac0 range (0=bipolar; 1=unipolar)
23 *   [8] - dac0 reference (0=internal; 1=external)
24 *   [9] - dac0 coding (0=2's comp; 1=straight binary)
25 *   [10] - dac1 range (same as dac0 options)
26 *   [11] - dac1 reference (same as dac0 options)
27 *   [12] - dac1 coding (same as dac0 options)
28 */
29
30/*
31 * I must give credit here to Michal Dobes <dobes@tesnet.cz> who
32 * wrote the driver for Advantec's pcl812 boards. I used the interrupt
33 * handling code from his driver as an example for this one.
34 *
35 * Chris Baugher
36 * 5/1/2000
37 *
38 */
39
40#include <linux/module.h>
41#include <linux/interrupt.h>
42#include <linux/comedi/comedidev.h>
43#include <linux/comedi/comedi_8255.h>
44
45/* Configuration and Status Registers */
46#define COM_REG_1	0x00	/* wo 16 */
47#define STAT_REG	0x00	/* ro 16 */
48#define COM_REG_2	0x02	/* wo 16 */
49/* Event Strobe Registers */
50#define START_CONVERT_REG	0x08	/* wo 16 */
51#define START_DAQ_REG		0x0A	/* wo 16 */
52#define AD_CLEAR_REG		0x0C	/* wo 16 */
53#define EXT_STROBE_REG		0x0E	/* wo 16 */
54/* Analog Output Registers */
55#define DAC0_REG		0x10	/* wo 16 */
56#define DAC1_REG		0x12	/* wo 16 */
57#define INT2CLR_REG		0x14	/* wo 16 */
58/* Analog Input Registers */
59#define MUX_CNTR_REG		0x04	/* wo 16 */
60#define MUX_GAIN_REG		0x06	/* wo 16 */
61#define AD_FIFO_REG		0x16	/* ro 16 */
62#define DMA_TC_INT_CLR_REG	0x16	/* wo 16 */
63/* AM9513A Counter/Timer Registers */
64#define AM9513A_DATA_REG	0x18	/* rw 16 */
65#define AM9513A_COM_REG		0x1A	/* wo 16 */
66#define AM9513A_STAT_REG	0x1A	/* ro 16 */
67/* MIO-16 Digital I/O Registers */
68#define MIO_16_DIG_IN_REG	0x1C	/* ro 16 */
69#define MIO_16_DIG_OUT_REG	0x1C	/* wo 16 */
70/* RTSI Switch Registers */
71#define RTSI_SW_SHIFT_REG	0x1E	/* wo 8 */
72#define RTSI_SW_STROBE_REG	0x1F	/* wo 8 */
73/* DIO-24 Registers */
74#define DIO_24_PORTA_REG	0x00	/* rw 8 */
75#define DIO_24_PORTB_REG	0x01	/* rw 8 */
76#define DIO_24_PORTC_REG	0x02	/* rw 8 */
77#define DIO_24_CNFG_REG		0x03	/* wo 8 */
78
79/* Command Register bits */
80#define COMREG1_2SCADC		0x0001
81#define COMREG1_1632CNT		0x0002
82#define COMREG1_SCANEN		0x0008
83#define COMREG1_DAQEN		0x0010
84#define COMREG1_DMAEN		0x0020
85#define COMREG1_CONVINTEN	0x0080
86#define COMREG2_SCN2		0x0010
87#define COMREG2_INTEN		0x0080
88#define COMREG2_DOUTEN0		0x0100
89#define COMREG2_DOUTEN1		0x0200
90/* Status Register bits */
91#define STAT_AD_OVERRUN		0x0100
92#define STAT_AD_OVERFLOW	0x0200
93#define STAT_AD_DAQPROG		0x0800
94#define STAT_AD_CONVAVAIL	0x2000
95#define STAT_AD_DAQSTOPINT	0x4000
96/* AM9513A Counter/Timer defines */
97#define CLOCK_1_MHZ		0x8B25
98#define CLOCK_100_KHZ	0x8C25
99#define CLOCK_10_KHZ	0x8D25
100#define CLOCK_1_KHZ		0x8E25
101#define CLOCK_100_HZ	0x8F25
102
103struct atmio16_board_t {
104	const char *name;
105	int has_8255;
106};
107
108/* range structs */
109static const struct comedi_lrange range_atmio16d_ai_10_bipolar = {
110	4, {
111		BIP_RANGE(10),
112		BIP_RANGE(1),
113		BIP_RANGE(0.1),
114		BIP_RANGE(0.02)
115	}
116};
117
118static const struct comedi_lrange range_atmio16d_ai_5_bipolar = {
119	4, {
120		BIP_RANGE(5),
121		BIP_RANGE(0.5),
122		BIP_RANGE(0.05),
123		BIP_RANGE(0.01)
124	}
125};
126
127static const struct comedi_lrange range_atmio16d_ai_unipolar = {
128	4, {
129		UNI_RANGE(10),
130		UNI_RANGE(1),
131		UNI_RANGE(0.1),
132		UNI_RANGE(0.02)
133	}
134};
135
136/* private data struct */
137struct atmio16d_private {
138	enum { adc_diff, adc_singleended } adc_mux;
139	enum { adc_bipolar10, adc_bipolar5, adc_unipolar10 } adc_range;
140	enum { adc_2comp, adc_straight } adc_coding;
141	enum { dac_bipolar, dac_unipolar } dac0_range, dac1_range;
142	enum { dac_internal, dac_external } dac0_reference, dac1_reference;
143	enum { dac_2comp, dac_straight } dac0_coding, dac1_coding;
144	const struct comedi_lrange *ao_range_type_list[2];
145	unsigned int com_reg_1_state; /* current state of command register 1 */
146	unsigned int com_reg_2_state; /* current state of command register 2 */
147};
148
149static void reset_counters(struct comedi_device *dev)
150{
151	/* Counter 2 */
152	outw(0xFFC2, dev->iobase + AM9513A_COM_REG);
153	outw(0xFF02, dev->iobase + AM9513A_COM_REG);
154	outw(0x4, dev->iobase + AM9513A_DATA_REG);
155	outw(0xFF0A, dev->iobase + AM9513A_COM_REG);
156	outw(0x3, dev->iobase + AM9513A_DATA_REG);
157	outw(0xFF42, dev->iobase + AM9513A_COM_REG);
158	outw(0xFF42, dev->iobase + AM9513A_COM_REG);
159	/* Counter 3 */
160	outw(0xFFC4, dev->iobase + AM9513A_COM_REG);
161	outw(0xFF03, dev->iobase + AM9513A_COM_REG);
162	outw(0x4, dev->iobase + AM9513A_DATA_REG);
163	outw(0xFF0B, dev->iobase + AM9513A_COM_REG);
164	outw(0x3, dev->iobase + AM9513A_DATA_REG);
165	outw(0xFF44, dev->iobase + AM9513A_COM_REG);
166	outw(0xFF44, dev->iobase + AM9513A_COM_REG);
167	/* Counter 4 */
168	outw(0xFFC8, dev->iobase + AM9513A_COM_REG);
169	outw(0xFF04, dev->iobase + AM9513A_COM_REG);
170	outw(0x4, dev->iobase + AM9513A_DATA_REG);
171	outw(0xFF0C, dev->iobase + AM9513A_COM_REG);
172	outw(0x3, dev->iobase + AM9513A_DATA_REG);
173	outw(0xFF48, dev->iobase + AM9513A_COM_REG);
174	outw(0xFF48, dev->iobase + AM9513A_COM_REG);
175	/* Counter 5 */
176	outw(0xFFD0, dev->iobase + AM9513A_COM_REG);
177	outw(0xFF05, dev->iobase + AM9513A_COM_REG);
178	outw(0x4, dev->iobase + AM9513A_DATA_REG);
179	outw(0xFF0D, dev->iobase + AM9513A_COM_REG);
180	outw(0x3, dev->iobase + AM9513A_DATA_REG);
181	outw(0xFF50, dev->iobase + AM9513A_COM_REG);
182	outw(0xFF50, dev->iobase + AM9513A_COM_REG);
183
184	outw(0, dev->iobase + AD_CLEAR_REG);
185}
186
187static void reset_atmio16d(struct comedi_device *dev)
188{
189	struct atmio16d_private *devpriv = dev->private;
190	int i;
191
192	/* now we need to initialize the board */
193	outw(0, dev->iobase + COM_REG_1);
194	outw(0, dev->iobase + COM_REG_2);
195	outw(0, dev->iobase + MUX_GAIN_REG);
196	/* init AM9513A timer */
197	outw(0xFFFF, dev->iobase + AM9513A_COM_REG);
198	outw(0xFFEF, dev->iobase + AM9513A_COM_REG);
199	outw(0xFF17, dev->iobase + AM9513A_COM_REG);
200	outw(0xF000, dev->iobase + AM9513A_DATA_REG);
201	for (i = 1; i <= 5; ++i) {
202		outw(0xFF00 + i, dev->iobase + AM9513A_COM_REG);
203		outw(0x0004, dev->iobase + AM9513A_DATA_REG);
204		outw(0xFF08 + i, dev->iobase + AM9513A_COM_REG);
205		outw(0x3, dev->iobase + AM9513A_DATA_REG);
206	}
207	outw(0xFF5F, dev->iobase + AM9513A_COM_REG);
208	/* timer init done */
209	outw(0, dev->iobase + AD_CLEAR_REG);
210	outw(0, dev->iobase + INT2CLR_REG);
211	/* select straight binary mode for Analog Input */
212	devpriv->com_reg_1_state |= 1;
213	outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
214	devpriv->adc_coding = adc_straight;
215	/* zero the analog outputs */
216	outw(2048, dev->iobase + DAC0_REG);
217	outw(2048, dev->iobase + DAC1_REG);
218}
219
220static irqreturn_t atmio16d_interrupt(int irq, void *d)
221{
222	struct comedi_device *dev = d;
223	struct comedi_subdevice *s = dev->read_subdev;
224	unsigned short val;
225
226	val = inw(dev->iobase + AD_FIFO_REG);
227	comedi_buf_write_samples(s, &val, 1);
228	comedi_handle_events(dev, s);
229
230	return IRQ_HANDLED;
231}
232
233static int atmio16d_ai_cmdtest(struct comedi_device *dev,
234			       struct comedi_subdevice *s,
235			       struct comedi_cmd *cmd)
236{
237	int err = 0;
238
239	/* Step 1 : check if triggers are trivially valid */
240
241	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
242	err |= comedi_check_trigger_src(&cmd->scan_begin_src,
243					TRIG_FOLLOW | TRIG_TIMER);
244	err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
245	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
246	err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
247
248	if (err)
249		return 1;
250
251	/* Step 2a : make sure trigger sources are unique */
252
253	err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
254	err |= comedi_check_trigger_is_unique(cmd->stop_src);
255
256	/* Step 2b : and mutually compatible */
257
258	if (err)
259		return 2;
260
261	/* Step 3: check if arguments are trivially valid */
262
263	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
264
265	if (cmd->scan_begin_src == TRIG_FOLLOW) {
266		/* internal trigger */
267		err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
268	}
269
270	err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
271	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
272					   cmd->chanlist_len);
273
274	if (cmd->stop_src == TRIG_COUNT)
275		err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
276	else	/* TRIG_NONE */
277		err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
278
279	if (err)
280		return 3;
281
282	return 0;
283}
284
285static int atmio16d_ai_cmd(struct comedi_device *dev,
286			   struct comedi_subdevice *s)
287{
288	struct atmio16d_private *devpriv = dev->private;
289	struct comedi_cmd *cmd = &s->async->cmd;
290	unsigned int timer, base_clock;
291	unsigned int sample_count, tmp, chan, gain;
292	int i;
293
294	/*
295	 * This is slowly becoming a working command interface.
296	 * It is still uber-experimental
297	 */
298
299	reset_counters(dev);
300
301	/* check if scanning multiple channels */
302	if (cmd->chanlist_len < 2) {
303		devpriv->com_reg_1_state &= ~COMREG1_SCANEN;
304		outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
305	} else {
306		devpriv->com_reg_1_state |= COMREG1_SCANEN;
307		devpriv->com_reg_2_state |= COMREG2_SCN2;
308		outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
309		outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2);
310	}
311
312	/* Setup the Mux-Gain Counter */
313	for (i = 0; i < cmd->chanlist_len; ++i) {
314		chan = CR_CHAN(cmd->chanlist[i]);
315		gain = CR_RANGE(cmd->chanlist[i]);
316		outw(i, dev->iobase + MUX_CNTR_REG);
317		tmp = chan | (gain << 6);
318		if (i == cmd->scan_end_arg - 1)
319			tmp |= 0x0010;	/* set LASTONE bit */
320		outw(tmp, dev->iobase + MUX_GAIN_REG);
321	}
322
323	/*
324	 * Now program the sample interval timer.
325	 * Figure out which clock to use then get an appropriate timer value.
326	 */
327	if (cmd->convert_arg < 65536000) {
328		base_clock = CLOCK_1_MHZ;
329		timer = cmd->convert_arg / 1000;
330	} else if (cmd->convert_arg < 655360000) {
331		base_clock = CLOCK_100_KHZ;
332		timer = cmd->convert_arg / 10000;
333	} else /* cmd->convert_arg < 6553600000 */ {
334		base_clock = CLOCK_10_KHZ;
335		timer = cmd->convert_arg / 100000;
336	}
337	outw(0xFF03, dev->iobase + AM9513A_COM_REG);
338	outw(base_clock, dev->iobase + AM9513A_DATA_REG);
339	outw(0xFF0B, dev->iobase + AM9513A_COM_REG);
340	outw(0x2, dev->iobase + AM9513A_DATA_REG);
341	outw(0xFF44, dev->iobase + AM9513A_COM_REG);
342	outw(0xFFF3, dev->iobase + AM9513A_COM_REG);
343	outw(timer, dev->iobase + AM9513A_DATA_REG);
344	outw(0xFF24, dev->iobase + AM9513A_COM_REG);
345
346	/* Now figure out how many samples to get */
347	/* and program the sample counter */
348	sample_count = cmd->stop_arg * cmd->scan_end_arg;
349	outw(0xFF04, dev->iobase + AM9513A_COM_REG);
350	outw(0x1025, dev->iobase + AM9513A_DATA_REG);
351	outw(0xFF0C, dev->iobase + AM9513A_COM_REG);
352	if (sample_count < 65536) {
353		/* use only Counter 4 */
354		outw(sample_count, dev->iobase + AM9513A_DATA_REG);
355		outw(0xFF48, dev->iobase + AM9513A_COM_REG);
356		outw(0xFFF4, dev->iobase + AM9513A_COM_REG);
357		outw(0xFF28, dev->iobase + AM9513A_COM_REG);
358		devpriv->com_reg_1_state &= ~COMREG1_1632CNT;
359		outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
360	} else {
361		/* Counter 4 and 5 are needed */
362
363		tmp = sample_count & 0xFFFF;
364		if (tmp)
365			outw(tmp - 1, dev->iobase + AM9513A_DATA_REG);
366		else
367			outw(0xFFFF, dev->iobase + AM9513A_DATA_REG);
368
369		outw(0xFF48, dev->iobase + AM9513A_COM_REG);
370		outw(0, dev->iobase + AM9513A_DATA_REG);
371		outw(0xFF28, dev->iobase + AM9513A_COM_REG);
372		outw(0xFF05, dev->iobase + AM9513A_COM_REG);
373		outw(0x25, dev->iobase + AM9513A_DATA_REG);
374		outw(0xFF0D, dev->iobase + AM9513A_COM_REG);
375		tmp = sample_count & 0xFFFF;
376		if ((tmp == 0) || (tmp == 1)) {
377			outw((sample_count >> 16) & 0xFFFF,
378			     dev->iobase + AM9513A_DATA_REG);
379		} else {
380			outw(((sample_count >> 16) & 0xFFFF) + 1,
381			     dev->iobase + AM9513A_DATA_REG);
382		}
383		outw(0xFF70, dev->iobase + AM9513A_COM_REG);
384		devpriv->com_reg_1_state |= COMREG1_1632CNT;
385		outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
386	}
387
388	/*
389	 * Program the scan interval timer ONLY IF SCANNING IS ENABLED.
390	 * Figure out which clock to use then get an appropriate timer value.
391	 */
392	if (cmd->chanlist_len > 1) {
393		if (cmd->scan_begin_arg < 65536000) {
394			base_clock = CLOCK_1_MHZ;
395			timer = cmd->scan_begin_arg / 1000;
396		} else if (cmd->scan_begin_arg < 655360000) {
397			base_clock = CLOCK_100_KHZ;
398			timer = cmd->scan_begin_arg / 10000;
399		} else /* cmd->scan_begin_arg < 6553600000 */ {
400			base_clock = CLOCK_10_KHZ;
401			timer = cmd->scan_begin_arg / 100000;
402		}
403		outw(0xFF02, dev->iobase + AM9513A_COM_REG);
404		outw(base_clock, dev->iobase + AM9513A_DATA_REG);
405		outw(0xFF0A, dev->iobase + AM9513A_COM_REG);
406		outw(0x2, dev->iobase + AM9513A_DATA_REG);
407		outw(0xFF42, dev->iobase + AM9513A_COM_REG);
408		outw(0xFFF2, dev->iobase + AM9513A_COM_REG);
409		outw(timer, dev->iobase + AM9513A_DATA_REG);
410		outw(0xFF22, dev->iobase + AM9513A_COM_REG);
411	}
412
413	/* Clear the A/D FIFO and reset the MUX counter */
414	outw(0, dev->iobase + AD_CLEAR_REG);
415	outw(0, dev->iobase + MUX_CNTR_REG);
416	outw(0, dev->iobase + INT2CLR_REG);
417	/* enable this acquisition operation */
418	devpriv->com_reg_1_state |= COMREG1_DAQEN;
419	outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
420	/* enable interrupts for conversion completion */
421	devpriv->com_reg_1_state |= COMREG1_CONVINTEN;
422	devpriv->com_reg_2_state |= COMREG2_INTEN;
423	outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
424	outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2);
425	/* apply a trigger. this starts the counters! */
426	outw(0, dev->iobase + START_DAQ_REG);
427
428	return 0;
429}
430
431/* This will cancel a running acquisition operation */
432static int atmio16d_ai_cancel(struct comedi_device *dev,
433			      struct comedi_subdevice *s)
434{
435	reset_atmio16d(dev);
436
437	return 0;
438}
439
440static int atmio16d_ai_eoc(struct comedi_device *dev,
441			   struct comedi_subdevice *s,
442			   struct comedi_insn *insn,
443			   unsigned long context)
444{
445	unsigned int status;
446
447	status = inw(dev->iobase + STAT_REG);
448	if (status & STAT_AD_CONVAVAIL)
449		return 0;
450	if (status & STAT_AD_OVERFLOW) {
451		outw(0, dev->iobase + AD_CLEAR_REG);
452		return -EOVERFLOW;
453	}
454	return -EBUSY;
455}
456
457static int atmio16d_ai_insn_read(struct comedi_device *dev,
458				 struct comedi_subdevice *s,
459				 struct comedi_insn *insn, unsigned int *data)
460{
461	struct atmio16d_private *devpriv = dev->private;
462	int i;
463	int chan;
464	int gain;
465	int ret;
466
467	chan = CR_CHAN(insn->chanspec);
468	gain = CR_RANGE(insn->chanspec);
469
470	/* reset the Analog input circuitry */
471	/* outw( 0, dev->iobase+AD_CLEAR_REG ); */
472	/* reset the Analog Input MUX Counter to 0 */
473	/* outw( 0, dev->iobase+MUX_CNTR_REG ); */
474
475	/* set the Input MUX gain */
476	outw(chan | (gain << 6), dev->iobase + MUX_GAIN_REG);
477
478	for (i = 0; i < insn->n; i++) {
479		/* start the conversion */
480		outw(0, dev->iobase + START_CONVERT_REG);
481
482		/* wait for it to finish */
483		ret = comedi_timeout(dev, s, insn, atmio16d_ai_eoc, 0);
484		if (ret)
485			return ret;
486
487		/* read the data now */
488		data[i] = inw(dev->iobase + AD_FIFO_REG);
489		/* change to two's complement if need be */
490		if (devpriv->adc_coding == adc_2comp)
491			data[i] ^= 0x800;
492	}
493
494	return i;
495}
496
497static int atmio16d_ao_insn_write(struct comedi_device *dev,
498				  struct comedi_subdevice *s,
499				  struct comedi_insn *insn,
500				  unsigned int *data)
501{
502	struct atmio16d_private *devpriv = dev->private;
503	unsigned int chan = CR_CHAN(insn->chanspec);
504	unsigned int reg = (chan) ? DAC1_REG : DAC0_REG;
505	bool munge = false;
506	int i;
507
508	if (chan == 0 && devpriv->dac0_coding == dac_2comp)
509		munge = true;
510	if (chan == 1 && devpriv->dac1_coding == dac_2comp)
511		munge = true;
512
513	for (i = 0; i < insn->n; i++) {
514		unsigned int val = data[i];
515
516		s->readback[chan] = val;
517
518		if (munge)
519			val ^= 0x800;
520
521		outw(val, dev->iobase + reg);
522	}
523
524	return insn->n;
525}
526
527static int atmio16d_dio_insn_bits(struct comedi_device *dev,
528				  struct comedi_subdevice *s,
529				  struct comedi_insn *insn,
530				  unsigned int *data)
531{
532	if (comedi_dio_update_state(s, data))
533		outw(s->state, dev->iobase + MIO_16_DIG_OUT_REG);
534
535	data[1] = inw(dev->iobase + MIO_16_DIG_IN_REG);
536
537	return insn->n;
538}
539
540static int atmio16d_dio_insn_config(struct comedi_device *dev,
541				    struct comedi_subdevice *s,
542				    struct comedi_insn *insn,
543				    unsigned int *data)
544{
545	struct atmio16d_private *devpriv = dev->private;
546	unsigned int chan = CR_CHAN(insn->chanspec);
547	unsigned int mask;
548	int ret;
549
550	if (chan < 4)
551		mask = 0x0f;
552	else
553		mask = 0xf0;
554
555	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
556	if (ret)
557		return ret;
558
559	devpriv->com_reg_2_state &= ~(COMREG2_DOUTEN0 | COMREG2_DOUTEN1);
560	if (s->io_bits & 0x0f)
561		devpriv->com_reg_2_state |= COMREG2_DOUTEN0;
562	if (s->io_bits & 0xf0)
563		devpriv->com_reg_2_state |= COMREG2_DOUTEN1;
564	outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2);
565
566	return insn->n;
567}
568
569static int atmio16d_attach(struct comedi_device *dev,
570			   struct comedi_devconfig *it)
571{
572	const struct atmio16_board_t *board = dev->board_ptr;
573	struct atmio16d_private *devpriv;
574	struct comedi_subdevice *s;
575	int ret;
576
577	ret = comedi_request_region(dev, it->options[0], 0x20);
578	if (ret)
579		return ret;
580
581	ret = comedi_alloc_subdevices(dev, 4);
582	if (ret)
583		return ret;
584
585	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
586	if (!devpriv)
587		return -ENOMEM;
588
589	/* reset the atmio16d hardware */
590	reset_atmio16d(dev);
591
592	if (it->options[1]) {
593		ret = request_irq(it->options[1], atmio16d_interrupt, 0,
594				  dev->board_name, dev);
595		if (ret == 0)
596			dev->irq = it->options[1];
597	}
598
599	/* set device options */
600	devpriv->adc_mux = it->options[5];
601	devpriv->adc_range = it->options[6];
602
603	devpriv->dac0_range = it->options[7];
604	devpriv->dac0_reference = it->options[8];
605	devpriv->dac0_coding = it->options[9];
606	devpriv->dac1_range = it->options[10];
607	devpriv->dac1_reference = it->options[11];
608	devpriv->dac1_coding = it->options[12];
609
610	/* setup sub-devices */
611	s = &dev->subdevices[0];
612	/* ai subdevice */
613	s->type = COMEDI_SUBD_AI;
614	s->subdev_flags = SDF_READABLE | SDF_GROUND;
615	s->n_chan = (devpriv->adc_mux ? 16 : 8);
616	s->insn_read = atmio16d_ai_insn_read;
617	s->maxdata = 0xfff;	/* 4095 decimal */
618	switch (devpriv->adc_range) {
619	case adc_bipolar10:
620		s->range_table = &range_atmio16d_ai_10_bipolar;
621		break;
622	case adc_bipolar5:
623		s->range_table = &range_atmio16d_ai_5_bipolar;
624		break;
625	case adc_unipolar10:
626		s->range_table = &range_atmio16d_ai_unipolar;
627		break;
628	}
629	if (dev->irq) {
630		dev->read_subdev = s;
631		s->subdev_flags |= SDF_CMD_READ;
632		s->len_chanlist = 16;
633		s->do_cmdtest = atmio16d_ai_cmdtest;
634		s->do_cmd = atmio16d_ai_cmd;
635		s->cancel = atmio16d_ai_cancel;
636	}
637
638	/* ao subdevice */
639	s = &dev->subdevices[1];
640	s->type = COMEDI_SUBD_AO;
641	s->subdev_flags = SDF_WRITABLE;
642	s->n_chan = 2;
643	s->maxdata = 0xfff;	/* 4095 decimal */
644	s->range_table_list = devpriv->ao_range_type_list;
645	switch (devpriv->dac0_range) {
646	case dac_bipolar:
647		devpriv->ao_range_type_list[0] = &range_bipolar10;
648		break;
649	case dac_unipolar:
650		devpriv->ao_range_type_list[0] = &range_unipolar10;
651		break;
652	}
653	switch (devpriv->dac1_range) {
654	case dac_bipolar:
655		devpriv->ao_range_type_list[1] = &range_bipolar10;
656		break;
657	case dac_unipolar:
658		devpriv->ao_range_type_list[1] = &range_unipolar10;
659		break;
660	}
661	s->insn_write = atmio16d_ao_insn_write;
662
663	ret = comedi_alloc_subdev_readback(s);
664	if (ret)
665		return ret;
666
667	/* Digital I/O */
668	s = &dev->subdevices[2];
669	s->type = COMEDI_SUBD_DIO;
670	s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
671	s->n_chan = 8;
672	s->insn_bits = atmio16d_dio_insn_bits;
673	s->insn_config = atmio16d_dio_insn_config;
674	s->maxdata = 1;
675	s->range_table = &range_digital;
676
677	/* 8255 subdevice */
678	s = &dev->subdevices[3];
679	if (board->has_8255) {
680		ret = subdev_8255_io_init(dev, s, 0x00);
681		if (ret)
682			return ret;
683	} else {
684		s->type = COMEDI_SUBD_UNUSED;
685	}
686
687/* don't yet know how to deal with counter/timers */
688#if 0
689	s = &dev->subdevices[4];
690	/* do */
691	s->type = COMEDI_SUBD_TIMER;
692	s->n_chan = 0;
693	s->maxdata = 0
694#endif
695
696	return 0;
697}
698
699static void atmio16d_detach(struct comedi_device *dev)
700{
701	reset_atmio16d(dev);
702	comedi_legacy_detach(dev);
703}
704
705static const struct atmio16_board_t atmio16_boards[] = {
706	{
707		.name		= "atmio16",
708		.has_8255	= 0,
709	}, {
710		.name		= "atmio16d",
711		.has_8255	= 1,
712	},
713};
714
715static struct comedi_driver atmio16d_driver = {
716	.driver_name	= "atmio16",
717	.module		= THIS_MODULE,
718	.attach		= atmio16d_attach,
719	.detach		= atmio16d_detach,
720	.board_name	= &atmio16_boards[0].name,
721	.num_names	= ARRAY_SIZE(atmio16_boards),
722	.offset		= sizeof(struct atmio16_board_t),
723};
724module_comedi_driver(atmio16d_driver);
725
726MODULE_AUTHOR("Comedi https://www.comedi.org");
727MODULE_DESCRIPTION("Comedi low-level driver");
728MODULE_LICENSE("GPL");
729