1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * comedi/drivers/ni_daq_700.c |
4 | * Driver for DAQCard-700 DIO/AI |
5 | * copied from 8255 |
6 | * |
7 | * COMEDI - Linux Control and Measurement Device Interface |
8 | * Copyright (C) 1998 David A. Schleef <ds@schleef.org> |
9 | */ |
10 | |
11 | /* |
12 | * Driver: ni_daq_700 |
13 | * Description: National Instruments PCMCIA DAQCard-700 |
14 | * Author: Fred Brooks <nsaspook@nsaspook.com>, |
15 | * based on ni_daq_dio24 by Daniel Vecino Castel <dvecino@able.es> |
16 | * Devices: [National Instruments] PCMCIA DAQ-Card-700 (ni_daq_700) |
17 | * Status: works |
18 | * Updated: Wed, 21 May 2014 12:07:20 +0000 |
19 | * |
20 | * The daqcard-700 appears in Comedi as a digital I/O subdevice (0) with |
21 | * 16 channels and a analog input subdevice (1) with 16 single-ended channels |
22 | * or 8 differential channels, and three input ranges. |
23 | * |
24 | * Digital: The channel 0 corresponds to the daqcard-700's output |
25 | * port, bit 0; channel 8 corresponds to the input port, bit 0. |
26 | * |
27 | * Digital direction configuration: channels 0-7 output, 8-15 input. |
28 | * |
29 | * Analog: The input range is 0 to 4095 with a default of -10 to +10 volts. |
30 | * Valid ranges: |
31 | * 0 for -10 to 10V bipolar |
32 | * 1 for -5 to 5V bipolar |
33 | * 2 for -2.5 to 2.5V bipolar |
34 | * |
35 | * IRQ is assigned but not used. |
36 | * |
37 | * Manuals: Register level: https://www.ni.com/pdf/manuals/340698.pdf |
38 | * User Manual: https://www.ni.com/pdf/manuals/320676d.pdf |
39 | */ |
40 | |
41 | #include <linux/module.h> |
42 | #include <linux/delay.h> |
43 | #include <linux/interrupt.h> |
44 | #include <linux/comedi/comedi_pcmcia.h> |
45 | |
46 | /* daqcard700 registers */ |
47 | #define DIO_W 0x04 /* WO 8bit */ |
48 | #define DIO_R 0x05 /* RO 8bit */ |
49 | #define CMD_R1 0x00 /* WO 8bit */ |
50 | #define CMD_R2 0x07 /* RW 8bit */ |
51 | #define CMD_R3 0x05 /* W0 8bit */ |
52 | #define STA_R1 0x00 /* RO 8bit */ |
53 | #define STA_R2 0x01 /* RO 8bit */ |
54 | #define ADFIFO_R 0x02 /* RO 16bit */ |
55 | #define ADCLEAR_R 0x01 /* WO 8bit */ |
56 | #define CDA_R0 0x08 /* RW 8bit */ |
57 | #define CDA_R1 0x09 /* RW 8bit */ |
58 | #define CDA_R2 0x0A /* RW 8bit */ |
59 | #define CMO_R 0x0B /* RO 8bit */ |
60 | #define TIC_R 0x06 /* WO 8bit */ |
61 | /* daqcard700 modes */ |
62 | #define CMD_R3_DIFF 0x04 /* diff mode */ |
63 | |
64 | static const struct comedi_lrange range_daq700_ai = { |
65 | 3, |
66 | { |
67 | BIP_RANGE(10), |
68 | BIP_RANGE(5), |
69 | BIP_RANGE(2.5) |
70 | } |
71 | }; |
72 | |
73 | static int daq700_dio_insn_bits(struct comedi_device *dev, |
74 | struct comedi_subdevice *s, |
75 | struct comedi_insn *insn, |
76 | unsigned int *data) |
77 | { |
78 | unsigned int mask; |
79 | unsigned int val; |
80 | |
81 | mask = comedi_dio_update_state(s, data); |
82 | if (mask) { |
83 | if (mask & 0xff) |
84 | outb(value: s->state & 0xff, port: dev->iobase + DIO_W); |
85 | } |
86 | |
87 | val = s->state & 0xff; |
88 | val |= inb(port: dev->iobase + DIO_R) << 8; |
89 | |
90 | data[1] = val; |
91 | |
92 | return insn->n; |
93 | } |
94 | |
95 | static int daq700_dio_insn_config(struct comedi_device *dev, |
96 | struct comedi_subdevice *s, |
97 | struct comedi_insn *insn, |
98 | unsigned int *data) |
99 | { |
100 | int ret; |
101 | |
102 | ret = comedi_dio_insn_config(dev, s, insn, data, mask: 0); |
103 | if (ret) |
104 | return ret; |
105 | |
106 | /* The DIO channels are not configurable, fix the io_bits */ |
107 | s->io_bits = 0x00ff; |
108 | |
109 | return insn->n; |
110 | } |
111 | |
112 | static int daq700_ai_eoc(struct comedi_device *dev, |
113 | struct comedi_subdevice *s, |
114 | struct comedi_insn *insn, |
115 | unsigned long context) |
116 | { |
117 | unsigned int status; |
118 | |
119 | status = inb(port: dev->iobase + STA_R2); |
120 | if ((status & 0x03)) |
121 | return -EOVERFLOW; |
122 | status = inb(port: dev->iobase + STA_R1); |
123 | if ((status & 0x02)) |
124 | return -ENODATA; |
125 | if ((status & 0x11) == 0x01) |
126 | return 0; |
127 | return -EBUSY; |
128 | } |
129 | |
130 | static int daq700_ai_rinsn(struct comedi_device *dev, |
131 | struct comedi_subdevice *s, |
132 | struct comedi_insn *insn, unsigned int *data) |
133 | { |
134 | int n; |
135 | int d; |
136 | int ret; |
137 | unsigned int chan = CR_CHAN(insn->chanspec); |
138 | unsigned int aref = CR_AREF(insn->chanspec); |
139 | unsigned int range = CR_RANGE(insn->chanspec); |
140 | unsigned int r3_bits = 0; |
141 | |
142 | /* set channel input modes */ |
143 | if (aref == AREF_DIFF) |
144 | r3_bits |= CMD_R3_DIFF; |
145 | /* write channel mode/range */ |
146 | if (range >= 1) |
147 | range++; /* convert range to hardware value */ |
148 | outb(value: r3_bits | (range & 0x03), port: dev->iobase + CMD_R3); |
149 | |
150 | /* write channel to multiplexer */ |
151 | /* set mask scan bit high to disable scanning */ |
152 | outb(value: chan | 0x80, port: dev->iobase + CMD_R1); |
153 | /* mux needs 2us to really settle [Fred Brooks]. */ |
154 | udelay(2); |
155 | |
156 | /* convert n samples */ |
157 | for (n = 0; n < insn->n; n++) { |
158 | /* trigger conversion with out0 L to H */ |
159 | outb(value: 0x00, port: dev->iobase + CMD_R2); /* enable ADC conversions */ |
160 | outb(value: 0x30, port: dev->iobase + CMO_R); /* mode 0 out0 L, from H */ |
161 | outb(value: 0x00, port: dev->iobase + ADCLEAR_R); /* clear the ADC FIFO */ |
162 | /* read 16bit junk from FIFO to clear */ |
163 | inw(port: dev->iobase + ADFIFO_R); |
164 | /* mode 1 out0 H, L to H, start conversion */ |
165 | outb(value: 0x32, port: dev->iobase + CMO_R); |
166 | |
167 | /* wait for conversion to end */ |
168 | ret = comedi_timeout(dev, s, insn, cb: daq700_ai_eoc, context: 0); |
169 | if (ret) |
170 | return ret; |
171 | |
172 | /* read data */ |
173 | d = inw(port: dev->iobase + ADFIFO_R); |
174 | /* mangle the data as necessary */ |
175 | /* Bipolar Offset Binary: 0 to 4095 for -10 to +10 */ |
176 | d &= 0x0fff; |
177 | d ^= 0x0800; |
178 | data[n] = d; |
179 | } |
180 | return n; |
181 | } |
182 | |
183 | /* |
184 | * Data acquisition is enabled. |
185 | * The counter 0 output is high. |
186 | * The I/O connector pin CLK1 drives counter 1 source. |
187 | * Multiple-channel scanning is disabled. |
188 | * All interrupts are disabled. |
189 | * The analog input range is set to +-10 V |
190 | * The analog input mode is single-ended. |
191 | * The analog input circuitry is initialized to channel 0. |
192 | * The A/D FIFO is cleared. |
193 | */ |
194 | static void daq700_ai_config(struct comedi_device *dev, |
195 | struct comedi_subdevice *s) |
196 | { |
197 | unsigned long iobase = dev->iobase; |
198 | |
199 | outb(value: 0x80, port: iobase + CMD_R1); /* disable scanning, ADC to chan 0 */ |
200 | outb(value: 0x00, port: iobase + CMD_R2); /* clear all bits */ |
201 | outb(value: 0x00, port: iobase + CMD_R3); /* set +-10 range */ |
202 | outb(value: 0x32, port: iobase + CMO_R); /* config counter mode1, out0 to H */ |
203 | outb(value: 0x00, port: iobase + TIC_R); /* clear counter interrupt */ |
204 | outb(value: 0x00, port: iobase + ADCLEAR_R); /* clear the ADC FIFO */ |
205 | inw(port: iobase + ADFIFO_R); /* read 16bit junk from FIFO to clear */ |
206 | } |
207 | |
208 | static int daq700_auto_attach(struct comedi_device *dev, |
209 | unsigned long context) |
210 | { |
211 | struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); |
212 | struct comedi_subdevice *s; |
213 | int ret; |
214 | |
215 | link->config_flags |= CONF_AUTO_SET_IO; |
216 | ret = comedi_pcmcia_enable(dev, NULL); |
217 | if (ret) |
218 | return ret; |
219 | dev->iobase = link->resource[0]->start; |
220 | |
221 | ret = comedi_alloc_subdevices(dev, num_subdevices: 2); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | /* DAQCard-700 dio */ |
226 | s = &dev->subdevices[0]; |
227 | s->type = COMEDI_SUBD_DIO; |
228 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
229 | s->n_chan = 16; |
230 | s->range_table = &range_digital; |
231 | s->maxdata = 1; |
232 | s->insn_bits = daq700_dio_insn_bits; |
233 | s->insn_config = daq700_dio_insn_config; |
234 | s->io_bits = 0x00ff; |
235 | |
236 | /* DAQCard-700 ai */ |
237 | s = &dev->subdevices[1]; |
238 | s->type = COMEDI_SUBD_AI; |
239 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; |
240 | s->n_chan = 16; |
241 | s->maxdata = BIT(12) - 1; |
242 | s->range_table = &range_daq700_ai; |
243 | s->insn_read = daq700_ai_rinsn; |
244 | daq700_ai_config(dev, s); |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | static struct comedi_driver daq700_driver = { |
250 | .driver_name = "ni_daq_700" , |
251 | .module = THIS_MODULE, |
252 | .auto_attach = daq700_auto_attach, |
253 | .detach = comedi_pcmcia_disable, |
254 | }; |
255 | |
256 | static int daq700_cs_attach(struct pcmcia_device *link) |
257 | { |
258 | return comedi_pcmcia_auto_config(link, driver: &daq700_driver); |
259 | } |
260 | |
261 | static const struct pcmcia_device_id daq700_cs_ids[] = { |
262 | PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4743), |
263 | PCMCIA_DEVICE_NULL |
264 | }; |
265 | MODULE_DEVICE_TABLE(pcmcia, daq700_cs_ids); |
266 | |
267 | static struct pcmcia_driver daq700_cs_driver = { |
268 | .name = "ni_daq_700" , |
269 | .owner = THIS_MODULE, |
270 | .id_table = daq700_cs_ids, |
271 | .probe = daq700_cs_attach, |
272 | .remove = comedi_pcmcia_auto_unconfig, |
273 | }; |
274 | module_comedi_pcmcia_driver(daq700_driver, daq700_cs_driver); |
275 | |
276 | MODULE_AUTHOR("Fred Brooks <nsaspook@nsaspook.com>" ); |
277 | MODULE_DESCRIPTION( |
278 | "Comedi driver for National Instruments PCMCIA DAQCard-700 DIO/AI" ); |
279 | MODULE_LICENSE("GPL" ); |
280 | |