1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * adv_pci1710.c |
4 | * Comedi driver for Advantech PCI-1710 series boards |
5 | * Author: Michal Dobes <dobes@tesnet.cz> |
6 | * |
7 | * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn> |
8 | * for testing and information. |
9 | */ |
10 | |
11 | /* |
12 | * Driver: adv_pci1710 |
13 | * Description: Comedi driver for Advantech PCI-1710 series boards |
14 | * Devices: [Advantech] PCI-1710 (adv_pci1710), PCI-1710HG, PCI-1711, |
15 | * PCI-1713, PCI-1731 |
16 | * Author: Michal Dobes <dobes@tesnet.cz> |
17 | * Updated: Fri, 29 Oct 2015 17:19:35 -0700 |
18 | * Status: works |
19 | * |
20 | * Configuration options: not applicable, uses PCI auto config |
21 | * |
22 | * This driver supports AI, AO, DI and DO subdevices. |
23 | * AI subdevice supports cmd and insn interface, |
24 | * other subdevices support only insn interface. |
25 | * |
26 | * The PCI-1710 and PCI-1710HG have the same PCI device ID, so the |
27 | * driver cannot distinguish between them, as would be normal for a |
28 | * PCI driver. |
29 | */ |
30 | |
31 | #include <linux/module.h> |
32 | #include <linux/interrupt.h> |
33 | #include <linux/comedi/comedi_pci.h> |
34 | #include <linux/comedi/comedi_8254.h> |
35 | |
36 | #include "amcc_s5933.h" |
37 | |
38 | /* |
39 | * PCI BAR2 Register map (dev->iobase) |
40 | */ |
41 | #define PCI171X_AD_DATA_REG 0x00 /* R: A/D data */ |
42 | #define PCI171X_SOFTTRG_REG 0x00 /* W: soft trigger for A/D */ |
43 | #define PCI171X_RANGE_REG 0x02 /* W: A/D gain/range register */ |
44 | #define PCI171X_RANGE_DIFF BIT(5) |
45 | #define PCI171X_RANGE_UNI BIT(4) |
46 | #define PCI171X_RANGE_GAIN(x) (((x) & 0x7) << 0) |
47 | #define PCI171X_MUX_REG 0x04 /* W: A/D multiplexor control */ |
48 | #define PCI171X_MUX_CHANH(x) (((x) & 0xff) << 8) |
49 | #define PCI171X_MUX_CHANL(x) (((x) & 0xff) << 0) |
50 | #define PCI171X_MUX_CHAN(x) (PCI171X_MUX_CHANH(x) | PCI171X_MUX_CHANL(x)) |
51 | #define PCI171X_STATUS_REG 0x06 /* R: status register */ |
52 | #define PCI171X_STATUS_IRQ BIT(11) /* 1=IRQ occurred */ |
53 | #define PCI171X_STATUS_FF BIT(10) /* 1=FIFO is full, fatal error */ |
54 | #define PCI171X_STATUS_FH BIT(9) /* 1=FIFO is half full */ |
55 | #define PCI171X_STATUS_FE BIT(8) /* 1=FIFO is empty */ |
56 | #define PCI171X_CTRL_REG 0x06 /* W: control register */ |
57 | #define PCI171X_CTRL_CNT0 BIT(6) /* 1=ext. clk, 0=int. 100kHz clk */ |
58 | #define PCI171X_CTRL_ONEFH BIT(5) /* 1=on FIFO half full, 0=on sample */ |
59 | #define PCI171X_CTRL_IRQEN BIT(4) /* 1=enable IRQ */ |
60 | #define PCI171X_CTRL_GATE BIT(3) /* 1=enable ext. trigger GATE (8254?) */ |
61 | #define PCI171X_CTRL_EXT BIT(2) /* 1=enable ext. trigger source */ |
62 | #define PCI171X_CTRL_PACER BIT(1) /* 1=enable int. 8254 trigger source */ |
63 | #define PCI171X_CTRL_SW BIT(0) /* 1=enable software trigger source */ |
64 | #define PCI171X_CLRINT_REG 0x08 /* W: clear interrupts request */ |
65 | #define PCI171X_CLRFIFO_REG 0x09 /* W: clear FIFO */ |
66 | #define PCI171X_DA_REG(x) (0x0a + ((x) * 2)) /* W: D/A register */ |
67 | #define PCI171X_DAREF_REG 0x0e /* W: D/A reference control */ |
68 | #define PCI171X_DAREF(c, r) (((r) & 0x3) << ((c) * 2)) |
69 | #define PCI171X_DAREF_MASK(c) PCI171X_DAREF((c), 0x3) |
70 | #define PCI171X_DI_REG 0x10 /* R: digital inputs */ |
71 | #define PCI171X_DO_REG 0x10 /* W: digital outputs */ |
72 | #define PCI171X_TIMER_BASE 0x18 /* R/W: 8254 timer */ |
73 | |
74 | static const struct comedi_lrange pci1710_ai_range = { |
75 | 9, { |
76 | BIP_RANGE(5), /* gain 1 (0x00) */ |
77 | BIP_RANGE(2.5), /* gain 2 (0x01) */ |
78 | BIP_RANGE(1.25), /* gain 4 (0x02) */ |
79 | BIP_RANGE(0.625), /* gain 8 (0x03) */ |
80 | BIP_RANGE(10), /* gain 0.5 (0x04) */ |
81 | UNI_RANGE(10), /* gain 1 (0x00 | UNI) */ |
82 | UNI_RANGE(5), /* gain 2 (0x01 | UNI) */ |
83 | UNI_RANGE(2.5), /* gain 4 (0x02 | UNI) */ |
84 | UNI_RANGE(1.25) /* gain 8 (0x03 | UNI) */ |
85 | } |
86 | }; |
87 | |
88 | static const struct comedi_lrange pci1710hg_ai_range = { |
89 | 12, { |
90 | BIP_RANGE(5), /* gain 1 (0x00) */ |
91 | BIP_RANGE(0.5), /* gain 10 (0x01) */ |
92 | BIP_RANGE(0.05), /* gain 100 (0x02) */ |
93 | BIP_RANGE(0.005), /* gain 1000 (0x03) */ |
94 | BIP_RANGE(10), /* gain 0.5 (0x04) */ |
95 | BIP_RANGE(1), /* gain 5 (0x05) */ |
96 | BIP_RANGE(0.1), /* gain 50 (0x06) */ |
97 | BIP_RANGE(0.01), /* gain 500 (0x07) */ |
98 | UNI_RANGE(10), /* gain 1 (0x00 | UNI) */ |
99 | UNI_RANGE(1), /* gain 10 (0x01 | UNI) */ |
100 | UNI_RANGE(0.1), /* gain 100 (0x02 | UNI) */ |
101 | UNI_RANGE(0.01) /* gain 1000 (0x03 | UNI) */ |
102 | } |
103 | }; |
104 | |
105 | static const struct comedi_lrange pci1711_ai_range = { |
106 | 5, { |
107 | BIP_RANGE(10), /* gain 1 (0x00) */ |
108 | BIP_RANGE(5), /* gain 2 (0x01) */ |
109 | BIP_RANGE(2.5), /* gain 4 (0x02) */ |
110 | BIP_RANGE(1.25), /* gain 8 (0x03) */ |
111 | BIP_RANGE(0.625) /* gain 16 (0x04) */ |
112 | } |
113 | }; |
114 | |
115 | static const struct comedi_lrange pci171x_ao_range = { |
116 | 3, { |
117 | UNI_RANGE(5), /* internal -5V ref */ |
118 | UNI_RANGE(10), /* internal -10V ref */ |
119 | RANGE_ext(0, 1) /* external -Vref (+/-10V max) */ |
120 | } |
121 | }; |
122 | |
123 | enum pci1710_boardid { |
124 | BOARD_PCI1710, |
125 | BOARD_PCI1710HG, |
126 | BOARD_PCI1711, |
127 | BOARD_PCI1713, |
128 | BOARD_PCI1731, |
129 | }; |
130 | |
131 | struct boardtype { |
132 | const char *name; |
133 | const struct comedi_lrange *ai_range; |
134 | unsigned int is_pci1711:1; |
135 | unsigned int is_pci1713:1; |
136 | unsigned int has_ao:1; |
137 | }; |
138 | |
139 | static const struct boardtype boardtypes[] = { |
140 | [BOARD_PCI1710] = { |
141 | .name = "pci1710" , |
142 | .ai_range = &pci1710_ai_range, |
143 | .has_ao = 1, |
144 | }, |
145 | [BOARD_PCI1710HG] = { |
146 | .name = "pci1710hg" , |
147 | .ai_range = &pci1710hg_ai_range, |
148 | .has_ao = 1, |
149 | }, |
150 | [BOARD_PCI1711] = { |
151 | .name = "pci1711" , |
152 | .ai_range = &pci1711_ai_range, |
153 | .is_pci1711 = 1, |
154 | .has_ao = 1, |
155 | }, |
156 | [BOARD_PCI1713] = { |
157 | .name = "pci1713" , |
158 | .ai_range = &pci1710_ai_range, |
159 | .is_pci1713 = 1, |
160 | }, |
161 | [BOARD_PCI1731] = { |
162 | .name = "pci1731" , |
163 | .ai_range = &pci1711_ai_range, |
164 | .is_pci1711 = 1, |
165 | }, |
166 | }; |
167 | |
168 | struct pci1710_private { |
169 | unsigned int max_samples; |
170 | unsigned int ctrl; /* control register value */ |
171 | unsigned int ctrl_ext; /* used to switch from TRIG_EXT to TRIG_xxx */ |
172 | unsigned int mux_scan; /* used to set the channel interval to scan */ |
173 | unsigned char ai_et; |
174 | unsigned int act_chanlist[32]; /* list of scanned channel */ |
175 | unsigned char saved_seglen; /* len of the non-repeating chanlist */ |
176 | unsigned char da_ranges; /* copy of D/A outpit range register */ |
177 | unsigned char unipolar_gain; /* adjust for unipolar gain codes */ |
178 | }; |
179 | |
180 | static int pci1710_ai_check_chanlist(struct comedi_device *dev, |
181 | struct comedi_subdevice *s, |
182 | struct comedi_cmd *cmd) |
183 | { |
184 | struct pci1710_private *devpriv = dev->private; |
185 | unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); |
186 | unsigned int last_aref = CR_AREF(cmd->chanlist[0]); |
187 | unsigned int next_chan = (chan0 + 1) % s->n_chan; |
188 | unsigned int chansegment[32]; |
189 | unsigned int seglen; |
190 | int i; |
191 | |
192 | if (cmd->chanlist_len == 1) { |
193 | devpriv->saved_seglen = cmd->chanlist_len; |
194 | return 0; |
195 | } |
196 | |
197 | /* first channel is always ok */ |
198 | chansegment[0] = cmd->chanlist[0]; |
199 | |
200 | for (i = 1; i < cmd->chanlist_len; i++) { |
201 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); |
202 | unsigned int aref = CR_AREF(cmd->chanlist[i]); |
203 | |
204 | if (cmd->chanlist[0] == cmd->chanlist[i]) |
205 | break; /* we detected a loop, stop */ |
206 | |
207 | if (aref == AREF_DIFF && (chan & 1)) { |
208 | dev_err(dev->class_dev, |
209 | "Odd channel cannot be differential input!\n" ); |
210 | return -EINVAL; |
211 | } |
212 | |
213 | if (last_aref == AREF_DIFF) |
214 | next_chan = (next_chan + 1) % s->n_chan; |
215 | if (chan != next_chan) { |
216 | dev_err(dev->class_dev, |
217 | "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n" , |
218 | i, chan, next_chan, chan0); |
219 | return -EINVAL; |
220 | } |
221 | |
222 | /* next correct channel in list */ |
223 | chansegment[i] = cmd->chanlist[i]; |
224 | last_aref = aref; |
225 | } |
226 | seglen = i; |
227 | |
228 | for (i = 0; i < cmd->chanlist_len; i++) { |
229 | if (cmd->chanlist[i] != chansegment[i % seglen]) { |
230 | dev_err(dev->class_dev, |
231 | "bad channel, reference or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n" , |
232 | i, CR_CHAN(chansegment[i]), |
233 | CR_RANGE(chansegment[i]), |
234 | CR_AREF(chansegment[i]), |
235 | CR_CHAN(cmd->chanlist[i % seglen]), |
236 | CR_RANGE(cmd->chanlist[i % seglen]), |
237 | CR_AREF(chansegment[i % seglen])); |
238 | return -EINVAL; |
239 | } |
240 | } |
241 | devpriv->saved_seglen = seglen; |
242 | |
243 | return 0; |
244 | } |
245 | |
246 | static void pci1710_ai_setup_chanlist(struct comedi_device *dev, |
247 | struct comedi_subdevice *s, |
248 | unsigned int *chanlist, |
249 | unsigned int n_chan, |
250 | unsigned int seglen) |
251 | { |
252 | struct pci1710_private *devpriv = dev->private; |
253 | unsigned int first_chan = CR_CHAN(chanlist[0]); |
254 | unsigned int last_chan = CR_CHAN(chanlist[seglen - 1]); |
255 | unsigned int i; |
256 | |
257 | for (i = 0; i < seglen; i++) { /* store range list to card */ |
258 | unsigned int chan = CR_CHAN(chanlist[i]); |
259 | unsigned int range = CR_RANGE(chanlist[i]); |
260 | unsigned int aref = CR_AREF(chanlist[i]); |
261 | unsigned int rangeval = 0; |
262 | |
263 | if (aref == AREF_DIFF) |
264 | rangeval |= PCI171X_RANGE_DIFF; |
265 | if (comedi_range_is_unipolar(s, range)) { |
266 | rangeval |= PCI171X_RANGE_UNI; |
267 | range -= devpriv->unipolar_gain; |
268 | } |
269 | rangeval |= PCI171X_RANGE_GAIN(range); |
270 | |
271 | /* select channel and set range */ |
272 | outw(PCI171X_MUX_CHAN(chan), port: dev->iobase + PCI171X_MUX_REG); |
273 | outw(value: rangeval, port: dev->iobase + PCI171X_RANGE_REG); |
274 | |
275 | devpriv->act_chanlist[i] = chan; |
276 | } |
277 | for ( ; i < n_chan; i++) /* store remainder of channel list */ |
278 | devpriv->act_chanlist[i] = CR_CHAN(chanlist[i]); |
279 | |
280 | /* select channel interval to scan */ |
281 | devpriv->mux_scan = PCI171X_MUX_CHANL(first_chan) | |
282 | PCI171X_MUX_CHANH(last_chan); |
283 | outw(value: devpriv->mux_scan, port: dev->iobase + PCI171X_MUX_REG); |
284 | } |
285 | |
286 | static int pci1710_ai_eoc(struct comedi_device *dev, |
287 | struct comedi_subdevice *s, |
288 | struct comedi_insn *insn, |
289 | unsigned long context) |
290 | { |
291 | unsigned int status; |
292 | |
293 | status = inw(port: dev->iobase + PCI171X_STATUS_REG); |
294 | if ((status & PCI171X_STATUS_FE) == 0) |
295 | return 0; |
296 | return -EBUSY; |
297 | } |
298 | |
299 | static int pci1710_ai_read_sample(struct comedi_device *dev, |
300 | struct comedi_subdevice *s, |
301 | unsigned int cur_chan, |
302 | unsigned short *val) |
303 | { |
304 | const struct boardtype *board = dev->board_ptr; |
305 | struct pci1710_private *devpriv = dev->private; |
306 | unsigned short sample; |
307 | unsigned int chan; |
308 | |
309 | sample = inw(port: dev->iobase + PCI171X_AD_DATA_REG); |
310 | if (!board->is_pci1713) { |
311 | /* |
312 | * The upper 4 bits of the 16-bit sample are the channel number |
313 | * that the sample was acquired from. Verify that this channel |
314 | * number matches the expected channel number. |
315 | */ |
316 | chan = sample >> 12; |
317 | if (chan != devpriv->act_chanlist[cur_chan]) { |
318 | dev_err(dev->class_dev, |
319 | "A/D data dropout: received from channel %d, expected %d\n" , |
320 | chan, devpriv->act_chanlist[cur_chan]); |
321 | return -ENODATA; |
322 | } |
323 | } |
324 | *val = sample & s->maxdata; |
325 | return 0; |
326 | } |
327 | |
328 | static int pci1710_ai_insn_read(struct comedi_device *dev, |
329 | struct comedi_subdevice *s, |
330 | struct comedi_insn *insn, |
331 | unsigned int *data) |
332 | { |
333 | struct pci1710_private *devpriv = dev->private; |
334 | int ret = 0; |
335 | int i; |
336 | |
337 | /* enable software trigger */ |
338 | devpriv->ctrl |= PCI171X_CTRL_SW; |
339 | outw(value: devpriv->ctrl, port: dev->iobase + PCI171X_CTRL_REG); |
340 | |
341 | outb(value: 0, port: dev->iobase + PCI171X_CLRFIFO_REG); |
342 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
343 | |
344 | pci1710_ai_setup_chanlist(dev, s, chanlist: &insn->chanspec, n_chan: 1, seglen: 1); |
345 | |
346 | for (i = 0; i < insn->n; i++) { |
347 | unsigned short val; |
348 | |
349 | /* start conversion */ |
350 | outw(value: 0, port: dev->iobase + PCI171X_SOFTTRG_REG); |
351 | |
352 | ret = comedi_timeout(dev, s, insn, cb: pci1710_ai_eoc, context: 0); |
353 | if (ret) |
354 | break; |
355 | |
356 | ret = pci1710_ai_read_sample(dev, s, cur_chan: 0, val: &val); |
357 | if (ret) |
358 | break; |
359 | |
360 | data[i] = val; |
361 | } |
362 | |
363 | /* disable software trigger */ |
364 | devpriv->ctrl &= ~PCI171X_CTRL_SW; |
365 | outw(value: devpriv->ctrl, port: dev->iobase + PCI171X_CTRL_REG); |
366 | |
367 | outb(value: 0, port: dev->iobase + PCI171X_CLRFIFO_REG); |
368 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
369 | |
370 | return ret ? ret : insn->n; |
371 | } |
372 | |
373 | static int pci1710_ai_cancel(struct comedi_device *dev, |
374 | struct comedi_subdevice *s) |
375 | { |
376 | struct pci1710_private *devpriv = dev->private; |
377 | |
378 | /* disable A/D triggers and interrupt sources */ |
379 | devpriv->ctrl &= PCI171X_CTRL_CNT0; /* preserve counter 0 clk src */ |
380 | outw(value: devpriv->ctrl, port: dev->iobase + PCI171X_CTRL_REG); |
381 | |
382 | /* disable pacer */ |
383 | comedi_8254_pacer_enable(i8254: dev->pacer, counter1: 1, counter2: 2, enable: false); |
384 | |
385 | /* clear A/D FIFO and any pending interrutps */ |
386 | outb(value: 0, port: dev->iobase + PCI171X_CLRFIFO_REG); |
387 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
388 | |
389 | return 0; |
390 | } |
391 | |
392 | static void pci1710_handle_every_sample(struct comedi_device *dev, |
393 | struct comedi_subdevice *s) |
394 | { |
395 | struct comedi_cmd *cmd = &s->async->cmd; |
396 | unsigned int status; |
397 | unsigned short val; |
398 | int ret; |
399 | |
400 | status = inw(port: dev->iobase + PCI171X_STATUS_REG); |
401 | if (status & PCI171X_STATUS_FE) { |
402 | dev_dbg(dev->class_dev, "A/D FIFO empty (%4x)\n" , status); |
403 | s->async->events |= COMEDI_CB_ERROR; |
404 | return; |
405 | } |
406 | if (status & PCI171X_STATUS_FF) { |
407 | dev_dbg(dev->class_dev, |
408 | "A/D FIFO Full status (Fatal Error!) (%4x)\n" , status); |
409 | s->async->events |= COMEDI_CB_ERROR; |
410 | return; |
411 | } |
412 | |
413 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
414 | |
415 | for (; !(inw(port: dev->iobase + PCI171X_STATUS_REG) & PCI171X_STATUS_FE);) { |
416 | ret = pci1710_ai_read_sample(dev, s, cur_chan: s->async->cur_chan, val: &val); |
417 | if (ret) { |
418 | s->async->events |= COMEDI_CB_ERROR; |
419 | break; |
420 | } |
421 | |
422 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
423 | |
424 | if (cmd->stop_src == TRIG_COUNT && |
425 | s->async->scans_done >= cmd->stop_arg) { |
426 | s->async->events |= COMEDI_CB_EOA; |
427 | break; |
428 | } |
429 | } |
430 | |
431 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
432 | } |
433 | |
434 | static void pci1710_handle_fifo(struct comedi_device *dev, |
435 | struct comedi_subdevice *s) |
436 | { |
437 | struct pci1710_private *devpriv = dev->private; |
438 | struct comedi_async *async = s->async; |
439 | struct comedi_cmd *cmd = &async->cmd; |
440 | unsigned int status; |
441 | int i; |
442 | |
443 | status = inw(port: dev->iobase + PCI171X_STATUS_REG); |
444 | if (!(status & PCI171X_STATUS_FH)) { |
445 | dev_dbg(dev->class_dev, "A/D FIFO not half full!\n" ); |
446 | async->events |= COMEDI_CB_ERROR; |
447 | return; |
448 | } |
449 | if (status & PCI171X_STATUS_FF) { |
450 | dev_dbg(dev->class_dev, |
451 | "A/D FIFO Full status (Fatal Error!)\n" ); |
452 | async->events |= COMEDI_CB_ERROR; |
453 | return; |
454 | } |
455 | |
456 | for (i = 0; i < devpriv->max_samples; i++) { |
457 | unsigned short val; |
458 | int ret; |
459 | |
460 | ret = pci1710_ai_read_sample(dev, s, cur_chan: s->async->cur_chan, val: &val); |
461 | if (ret) { |
462 | s->async->events |= COMEDI_CB_ERROR; |
463 | break; |
464 | } |
465 | |
466 | if (!comedi_buf_write_samples(s, data: &val, nsamples: 1)) |
467 | break; |
468 | |
469 | if (cmd->stop_src == TRIG_COUNT && |
470 | async->scans_done >= cmd->stop_arg) { |
471 | async->events |= COMEDI_CB_EOA; |
472 | break; |
473 | } |
474 | } |
475 | |
476 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
477 | } |
478 | |
479 | static irqreturn_t pci1710_irq_handler(int irq, void *d) |
480 | { |
481 | struct comedi_device *dev = d; |
482 | struct pci1710_private *devpriv = dev->private; |
483 | struct comedi_subdevice *s; |
484 | struct comedi_cmd *cmd; |
485 | |
486 | if (!dev->attached) /* is device attached? */ |
487 | return IRQ_NONE; /* no, exit */ |
488 | |
489 | s = dev->read_subdev; |
490 | cmd = &s->async->cmd; |
491 | |
492 | /* is this interrupt from our board? */ |
493 | if (!(inw(port: dev->iobase + PCI171X_STATUS_REG) & PCI171X_STATUS_IRQ)) |
494 | return IRQ_NONE; /* no, exit */ |
495 | |
496 | if (devpriv->ai_et) { /* Switch from initial TRIG_EXT to TRIG_xxx. */ |
497 | devpriv->ai_et = 0; |
498 | devpriv->ctrl &= PCI171X_CTRL_CNT0; |
499 | devpriv->ctrl |= PCI171X_CTRL_SW; /* set software trigger */ |
500 | outw(value: devpriv->ctrl, port: dev->iobase + PCI171X_CTRL_REG); |
501 | devpriv->ctrl = devpriv->ctrl_ext; |
502 | outb(value: 0, port: dev->iobase + PCI171X_CLRFIFO_REG); |
503 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
504 | /* no sample on this interrupt; reset the channel interval */ |
505 | outw(value: devpriv->mux_scan, port: dev->iobase + PCI171X_MUX_REG); |
506 | outw(value: devpriv->ctrl, port: dev->iobase + PCI171X_CTRL_REG); |
507 | comedi_8254_pacer_enable(i8254: dev->pacer, counter1: 1, counter2: 2, enable: true); |
508 | return IRQ_HANDLED; |
509 | } |
510 | |
511 | if (cmd->flags & CMDF_WAKE_EOS) |
512 | pci1710_handle_every_sample(dev, s); |
513 | else |
514 | pci1710_handle_fifo(dev, s); |
515 | |
516 | comedi_handle_events(dev, s); |
517 | |
518 | return IRQ_HANDLED; |
519 | } |
520 | |
521 | static int pci1710_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
522 | { |
523 | struct pci1710_private *devpriv = dev->private; |
524 | struct comedi_cmd *cmd = &s->async->cmd; |
525 | |
526 | pci1710_ai_setup_chanlist(dev, s, chanlist: cmd->chanlist, n_chan: cmd->chanlist_len, |
527 | seglen: devpriv->saved_seglen); |
528 | |
529 | outb(value: 0, port: dev->iobase + PCI171X_CLRFIFO_REG); |
530 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
531 | |
532 | devpriv->ctrl &= PCI171X_CTRL_CNT0; |
533 | if ((cmd->flags & CMDF_WAKE_EOS) == 0) |
534 | devpriv->ctrl |= PCI171X_CTRL_ONEFH; |
535 | |
536 | if (cmd->convert_src == TRIG_TIMER) { |
537 | comedi_8254_update_divisors(i8254: dev->pacer); |
538 | |
539 | devpriv->ctrl |= PCI171X_CTRL_PACER | PCI171X_CTRL_IRQEN; |
540 | if (cmd->start_src == TRIG_EXT) { |
541 | devpriv->ctrl_ext = devpriv->ctrl; |
542 | devpriv->ctrl &= ~(PCI171X_CTRL_PACER | |
543 | PCI171X_CTRL_ONEFH | |
544 | PCI171X_CTRL_GATE); |
545 | devpriv->ctrl |= PCI171X_CTRL_EXT; |
546 | devpriv->ai_et = 1; |
547 | } else { /* TRIG_NOW */ |
548 | devpriv->ai_et = 0; |
549 | } |
550 | outw(value: devpriv->ctrl, port: dev->iobase + PCI171X_CTRL_REG); |
551 | |
552 | if (cmd->start_src == TRIG_NOW) |
553 | comedi_8254_pacer_enable(i8254: dev->pacer, counter1: 1, counter2: 2, enable: true); |
554 | } else { /* TRIG_EXT */ |
555 | devpriv->ctrl |= PCI171X_CTRL_EXT | PCI171X_CTRL_IRQEN; |
556 | outw(value: devpriv->ctrl, port: dev->iobase + PCI171X_CTRL_REG); |
557 | } |
558 | |
559 | return 0; |
560 | } |
561 | |
562 | static int pci1710_ai_cmdtest(struct comedi_device *dev, |
563 | struct comedi_subdevice *s, |
564 | struct comedi_cmd *cmd) |
565 | { |
566 | int err = 0; |
567 | |
568 | /* Step 1 : check if triggers are trivially valid */ |
569 | |
570 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW | TRIG_EXT); |
571 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_FOLLOW); |
572 | err |= comedi_check_trigger_src(src: &cmd->convert_src, |
573 | TRIG_TIMER | TRIG_EXT); |
574 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
575 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
576 | |
577 | if (err) |
578 | return 1; |
579 | |
580 | /* step 2a: make sure trigger sources are unique */ |
581 | |
582 | err |= comedi_check_trigger_is_unique(src: cmd->start_src); |
583 | err |= comedi_check_trigger_is_unique(src: cmd->convert_src); |
584 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
585 | |
586 | /* step 2b: and mutually compatible */ |
587 | |
588 | if (err) |
589 | return 2; |
590 | |
591 | /* Step 3: check if arguments are trivially valid */ |
592 | |
593 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
594 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
595 | |
596 | if (cmd->convert_src == TRIG_TIMER) |
597 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, val: 10000); |
598 | else /* TRIG_FOLLOW */ |
599 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
600 | |
601 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
602 | val: cmd->chanlist_len); |
603 | |
604 | if (cmd->stop_src == TRIG_COUNT) |
605 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
606 | else /* TRIG_NONE */ |
607 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
608 | |
609 | if (err) |
610 | return 3; |
611 | |
612 | /* step 4: fix up any arguments */ |
613 | |
614 | if (cmd->convert_src == TRIG_TIMER) { |
615 | unsigned int arg = cmd->convert_arg; |
616 | |
617 | comedi_8254_cascade_ns_to_timer(i8254: dev->pacer, nanosec: &arg, flags: cmd->flags); |
618 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
619 | } |
620 | |
621 | if (err) |
622 | return 4; |
623 | |
624 | /* Step 5: check channel list */ |
625 | |
626 | err |= pci1710_ai_check_chanlist(dev, s, cmd); |
627 | |
628 | if (err) |
629 | return 5; |
630 | |
631 | return 0; |
632 | } |
633 | |
634 | static int pci1710_ao_insn_write(struct comedi_device *dev, |
635 | struct comedi_subdevice *s, |
636 | struct comedi_insn *insn, |
637 | unsigned int *data) |
638 | { |
639 | struct pci1710_private *devpriv = dev->private; |
640 | unsigned int chan = CR_CHAN(insn->chanspec); |
641 | unsigned int range = CR_RANGE(insn->chanspec); |
642 | unsigned int val = s->readback[chan]; |
643 | int i; |
644 | |
645 | devpriv->da_ranges &= ~PCI171X_DAREF_MASK(chan); |
646 | devpriv->da_ranges |= PCI171X_DAREF(chan, range); |
647 | outw(value: devpriv->da_ranges, port: dev->iobase + PCI171X_DAREF_REG); |
648 | |
649 | for (i = 0; i < insn->n; i++) { |
650 | val = data[i]; |
651 | outw(value: val, port: dev->iobase + PCI171X_DA_REG(chan)); |
652 | } |
653 | |
654 | s->readback[chan] = val; |
655 | |
656 | return insn->n; |
657 | } |
658 | |
659 | static int pci1710_di_insn_bits(struct comedi_device *dev, |
660 | struct comedi_subdevice *s, |
661 | struct comedi_insn *insn, |
662 | unsigned int *data) |
663 | { |
664 | data[1] = inw(port: dev->iobase + PCI171X_DI_REG); |
665 | |
666 | return insn->n; |
667 | } |
668 | |
669 | static int pci1710_do_insn_bits(struct comedi_device *dev, |
670 | struct comedi_subdevice *s, |
671 | struct comedi_insn *insn, |
672 | unsigned int *data) |
673 | { |
674 | if (comedi_dio_update_state(s, data)) |
675 | outw(value: s->state, port: dev->iobase + PCI171X_DO_REG); |
676 | |
677 | data[1] = s->state; |
678 | |
679 | return insn->n; |
680 | } |
681 | |
682 | static int pci1710_counter_insn_config(struct comedi_device *dev, |
683 | struct comedi_subdevice *s, |
684 | struct comedi_insn *insn, |
685 | unsigned int *data) |
686 | { |
687 | struct pci1710_private *devpriv = dev->private; |
688 | |
689 | switch (data[0]) { |
690 | case INSN_CONFIG_SET_CLOCK_SRC: |
691 | switch (data[1]) { |
692 | case 0: /* internal */ |
693 | devpriv->ctrl_ext &= ~PCI171X_CTRL_CNT0; |
694 | break; |
695 | case 1: /* external */ |
696 | devpriv->ctrl_ext |= PCI171X_CTRL_CNT0; |
697 | break; |
698 | default: |
699 | return -EINVAL; |
700 | } |
701 | outw(value: devpriv->ctrl_ext, port: dev->iobase + PCI171X_CTRL_REG); |
702 | break; |
703 | case INSN_CONFIG_GET_CLOCK_SRC: |
704 | if (devpriv->ctrl_ext & PCI171X_CTRL_CNT0) { |
705 | data[1] = 1; |
706 | data[2] = 0; |
707 | } else { |
708 | data[1] = 0; |
709 | data[2] = I8254_OSC_BASE_1MHZ; |
710 | } |
711 | break; |
712 | default: |
713 | return -EINVAL; |
714 | } |
715 | |
716 | return insn->n; |
717 | } |
718 | |
719 | static void pci1710_reset(struct comedi_device *dev) |
720 | { |
721 | const struct boardtype *board = dev->board_ptr; |
722 | |
723 | /* |
724 | * Disable A/D triggers and interrupt sources, set counter 0 |
725 | * to use internal 1 MHz clock. |
726 | */ |
727 | outw(value: 0, port: dev->iobase + PCI171X_CTRL_REG); |
728 | |
729 | /* clear A/D FIFO and any pending interrutps */ |
730 | outb(value: 0, port: dev->iobase + PCI171X_CLRFIFO_REG); |
731 | outb(value: 0, port: dev->iobase + PCI171X_CLRINT_REG); |
732 | |
733 | if (board->has_ao) { |
734 | /* set DACs to 0..5V and outputs to 0V */ |
735 | outb(value: 0, port: dev->iobase + PCI171X_DAREF_REG); |
736 | outw(value: 0, port: dev->iobase + PCI171X_DA_REG(0)); |
737 | outw(value: 0, port: dev->iobase + PCI171X_DA_REG(1)); |
738 | } |
739 | |
740 | /* set digital outputs to 0 */ |
741 | outw(value: 0, port: dev->iobase + PCI171X_DO_REG); |
742 | } |
743 | |
744 | static int pci1710_auto_attach(struct comedi_device *dev, |
745 | unsigned long context) |
746 | { |
747 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
748 | const struct boardtype *board = NULL; |
749 | struct pci1710_private *devpriv; |
750 | struct comedi_subdevice *s; |
751 | int ret, subdev, n_subdevices; |
752 | int i; |
753 | |
754 | if (context < ARRAY_SIZE(boardtypes)) |
755 | board = &boardtypes[context]; |
756 | if (!board) |
757 | return -ENODEV; |
758 | dev->board_ptr = board; |
759 | dev->board_name = board->name; |
760 | |
761 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
762 | if (!devpriv) |
763 | return -ENOMEM; |
764 | |
765 | ret = comedi_pci_enable(dev); |
766 | if (ret) |
767 | return ret; |
768 | dev->iobase = pci_resource_start(pcidev, 2); |
769 | |
770 | dev->pacer = comedi_8254_io_alloc(iobase: dev->iobase + PCI171X_TIMER_BASE, |
771 | I8254_OSC_BASE_10MHZ, I8254_IO16, regshift: 0); |
772 | if (IS_ERR(ptr: dev->pacer)) |
773 | return PTR_ERR(ptr: dev->pacer); |
774 | |
775 | n_subdevices = 1; /* all boards have analog inputs */ |
776 | if (board->has_ao) |
777 | n_subdevices++; |
778 | if (!board->is_pci1713) { |
779 | /* |
780 | * All other boards have digital inputs and outputs as |
781 | * well as a user counter. |
782 | */ |
783 | n_subdevices += 3; |
784 | } |
785 | |
786 | ret = comedi_alloc_subdevices(dev, num_subdevices: n_subdevices); |
787 | if (ret) |
788 | return ret; |
789 | |
790 | pci1710_reset(dev); |
791 | |
792 | if (pcidev->irq) { |
793 | ret = request_irq(irq: pcidev->irq, handler: pci1710_irq_handler, |
794 | IRQF_SHARED, name: dev->board_name, dev); |
795 | if (ret == 0) |
796 | dev->irq = pcidev->irq; |
797 | } |
798 | |
799 | subdev = 0; |
800 | |
801 | /* Analog Input subdevice */ |
802 | s = &dev->subdevices[subdev++]; |
803 | s->type = COMEDI_SUBD_AI; |
804 | s->subdev_flags = SDF_READABLE | SDF_GROUND; |
805 | if (!board->is_pci1711) |
806 | s->subdev_flags |= SDF_DIFF; |
807 | s->n_chan = board->is_pci1713 ? 32 : 16; |
808 | s->maxdata = 0x0fff; |
809 | s->range_table = board->ai_range; |
810 | s->insn_read = pci1710_ai_insn_read; |
811 | if (dev->irq) { |
812 | dev->read_subdev = s; |
813 | s->subdev_flags |= SDF_CMD_READ; |
814 | s->len_chanlist = s->n_chan; |
815 | s->do_cmdtest = pci1710_ai_cmdtest; |
816 | s->do_cmd = pci1710_ai_cmd; |
817 | s->cancel = pci1710_ai_cancel; |
818 | } |
819 | |
820 | /* find the value needed to adjust for unipolar gain codes */ |
821 | for (i = 0; i < s->range_table->length; i++) { |
822 | if (comedi_range_is_unipolar(s, range: i)) { |
823 | devpriv->unipolar_gain = i; |
824 | break; |
825 | } |
826 | } |
827 | |
828 | if (board->has_ao) { |
829 | /* Analog Output subdevice */ |
830 | s = &dev->subdevices[subdev++]; |
831 | s->type = COMEDI_SUBD_AO; |
832 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND; |
833 | s->n_chan = 2; |
834 | s->maxdata = 0x0fff; |
835 | s->range_table = &pci171x_ao_range; |
836 | s->insn_write = pci1710_ao_insn_write; |
837 | |
838 | ret = comedi_alloc_subdev_readback(s); |
839 | if (ret) |
840 | return ret; |
841 | } |
842 | |
843 | if (!board->is_pci1713) { |
844 | /* Digital Input subdevice */ |
845 | s = &dev->subdevices[subdev++]; |
846 | s->type = COMEDI_SUBD_DI; |
847 | s->subdev_flags = SDF_READABLE; |
848 | s->n_chan = 16; |
849 | s->maxdata = 1; |
850 | s->range_table = &range_digital; |
851 | s->insn_bits = pci1710_di_insn_bits; |
852 | |
853 | /* Digital Output subdevice */ |
854 | s = &dev->subdevices[subdev++]; |
855 | s->type = COMEDI_SUBD_DO; |
856 | s->subdev_flags = SDF_WRITABLE; |
857 | s->n_chan = 16; |
858 | s->maxdata = 1; |
859 | s->range_table = &range_digital; |
860 | s->insn_bits = pci1710_do_insn_bits; |
861 | |
862 | /* Counter subdevice (8254) */ |
863 | s = &dev->subdevices[subdev++]; |
864 | comedi_8254_subdevice_init(s, i8254: dev->pacer); |
865 | |
866 | dev->pacer->insn_config = pci1710_counter_insn_config; |
867 | |
868 | /* counters 1 and 2 are used internally for the pacer */ |
869 | comedi_8254_set_busy(i8254: dev->pacer, counter: 1, busy: true); |
870 | comedi_8254_set_busy(i8254: dev->pacer, counter: 2, busy: true); |
871 | } |
872 | |
873 | /* max_samples is half the FIFO size (2 bytes/sample) */ |
874 | devpriv->max_samples = (board->is_pci1711) ? 512 : 2048; |
875 | |
876 | return 0; |
877 | } |
878 | |
879 | static struct comedi_driver adv_pci1710_driver = { |
880 | .driver_name = "adv_pci1710" , |
881 | .module = THIS_MODULE, |
882 | .auto_attach = pci1710_auto_attach, |
883 | .detach = comedi_pci_detach, |
884 | }; |
885 | |
886 | static int adv_pci1710_pci_probe(struct pci_dev *dev, |
887 | const struct pci_device_id *id) |
888 | { |
889 | return comedi_pci_auto_config(pcidev: dev, driver: &adv_pci1710_driver, |
890 | context: id->driver_data); |
891 | } |
892 | |
893 | static const struct pci_device_id adv_pci1710_pci_table[] = { |
894 | { |
895 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
896 | PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050), |
897 | .driver_data = BOARD_PCI1710, |
898 | }, { |
899 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
900 | PCI_VENDOR_ID_ADVANTECH, 0x0000), |
901 | .driver_data = BOARD_PCI1710, |
902 | }, { |
903 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
904 | PCI_VENDOR_ID_ADVANTECH, 0xb100), |
905 | .driver_data = BOARD_PCI1710, |
906 | }, { |
907 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
908 | PCI_VENDOR_ID_ADVANTECH, 0xb200), |
909 | .driver_data = BOARD_PCI1710, |
910 | }, { |
911 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
912 | PCI_VENDOR_ID_ADVANTECH, 0xc100), |
913 | .driver_data = BOARD_PCI1710, |
914 | }, { |
915 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
916 | PCI_VENDOR_ID_ADVANTECH, 0xc200), |
917 | .driver_data = BOARD_PCI1710, |
918 | }, { |
919 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd100), |
920 | .driver_data = BOARD_PCI1710, |
921 | }, { |
922 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
923 | PCI_VENDOR_ID_ADVANTECH, 0x0002), |
924 | .driver_data = BOARD_PCI1710HG, |
925 | }, { |
926 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
927 | PCI_VENDOR_ID_ADVANTECH, 0xb102), |
928 | .driver_data = BOARD_PCI1710HG, |
929 | }, { |
930 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
931 | PCI_VENDOR_ID_ADVANTECH, 0xb202), |
932 | .driver_data = BOARD_PCI1710HG, |
933 | }, { |
934 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
935 | PCI_VENDOR_ID_ADVANTECH, 0xc102), |
936 | .driver_data = BOARD_PCI1710HG, |
937 | }, { |
938 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, |
939 | PCI_VENDOR_ID_ADVANTECH, 0xc202), |
940 | .driver_data = BOARD_PCI1710HG, |
941 | }, { |
942 | PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd102), |
943 | .driver_data = BOARD_PCI1710HG, |
944 | }, |
945 | { PCI_VDEVICE(ADVANTECH, 0x1711), BOARD_PCI1711 }, |
946 | { PCI_VDEVICE(ADVANTECH, 0x1713), BOARD_PCI1713 }, |
947 | { PCI_VDEVICE(ADVANTECH, 0x1731), BOARD_PCI1731 }, |
948 | { 0 } |
949 | }; |
950 | MODULE_DEVICE_TABLE(pci, adv_pci1710_pci_table); |
951 | |
952 | static struct pci_driver adv_pci1710_pci_driver = { |
953 | .name = "adv_pci1710" , |
954 | .id_table = adv_pci1710_pci_table, |
955 | .probe = adv_pci1710_pci_probe, |
956 | .remove = comedi_pci_auto_unconfig, |
957 | }; |
958 | module_comedi_pci_driver(adv_pci1710_driver, adv_pci1710_pci_driver); |
959 | |
960 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
961 | MODULE_DESCRIPTION("Comedi: Advantech PCI-1710 Series Multifunction DAS Cards" ); |
962 | MODULE_LICENSE("GPL" ); |
963 | |