Commit | Line | Data |
---|---|---|
dd2996b3 JG |
1 | /* |
2 | comedi/drivers/pcl816.c | |
3 | ||
4 | Author: Juan Grigera <juan@grigera.com.ar> | |
5 | based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812 | |
6 | ||
7 | hardware driver for Advantech cards: | |
8 | card: PCL-816, PCL814B | |
9 | driver: pcl816 | |
10 | */ | |
11 | /* | |
12 | Driver: pcl816 | |
13 | Description: Advantech PCL-816 cards, PCL-814 | |
14 | Author: Juan Grigera <juan@grigera.com.ar> | |
15 | Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b) | |
16 | Status: works | |
17 | Updated: Tue, 2 Apr 2002 23:15:21 -0800 | |
18 | ||
19 | PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO. | |
20 | Differences are at resolution (16 vs 12 bits). | |
21 | ||
22 | The driver support AI command mode, other subdevices not written. | |
23 | ||
24 | Analog output and digital input and output are not supported. | |
25 | ||
26 | Configuration Options: | |
27 | [0] - IO Base | |
28 | [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) | |
29 | [2] - DMA (0=disable, 1, 3) | |
30 | [3] - 0, 10=10MHz clock for 8254 | |
31 | 1= 1MHz clock for 8254 | |
32 | ||
33 | */ | |
34 | ||
35 | #include "../comedidev.h" | |
36 | ||
37 | #include <linux/ioport.h> | |
38 | #include <linux/mc146818rtc.h> | |
39 | #include <linux/delay.h> | |
40 | #include <asm/dma.h> | |
41 | ||
42 | #include "8253.h" | |
43 | ||
44 | #define DEBUG(x) x | |
45 | ||
58c0576e BP |
46 | /* boards constants */ |
47 | /* IO space len */ | |
dd2996b3 JG |
48 | #define PCLx1x_RANGE 16 |
49 | ||
58c0576e | 50 | /* #define outb(x,y) printk("OUTB(%x, 200+%d)\n", x,y-0x200); outb(x,y) */ |
dd2996b3 | 51 | |
58c0576e | 52 | /* INTEL 8254 counters */ |
dd2996b3 JG |
53 | #define PCL816_CTR0 4 |
54 | #define PCL816_CTR1 5 | |
55 | #define PCL816_CTR2 6 | |
58c0576e | 56 | /* R: counter read-back register W: counter control */ |
dd2996b3 JG |
57 | #define PCL816_CTRCTL 7 |
58 | ||
58c0576e | 59 | /* R: A/D high byte W: A/D range control */ |
dd2996b3 | 60 | #define PCL816_RANGE 9 |
58c0576e | 61 | /* W: clear INT request */ |
dd2996b3 | 62 | #define PCL816_CLRINT 10 |
58c0576e | 63 | /* R: next mux scan channel W: mux scan channel & range control pointer */ |
dd2996b3 | 64 | #define PCL816_MUX 11 |
58c0576e | 65 | /* R/W: operation control register */ |
dd2996b3 JG |
66 | #define PCL816_CONTROL 12 |
67 | ||
58c0576e | 68 | /* R: return status byte W: set DMA/IRQ */ |
dd2996b3 JG |
69 | #define PCL816_STATUS 13 |
70 | #define PCL816_STATUS_DRDY_MASK 0x80 | |
71 | ||
58c0576e | 72 | /* R: low byte of A/D W: soft A/D trigger */ |
dd2996b3 | 73 | #define PCL816_AD_LO 8 |
58c0576e | 74 | /* R: high byte of A/D W: A/D range control */ |
dd2996b3 JG |
75 | #define PCL816_AD_HI 9 |
76 | ||
58c0576e | 77 | /* type of interrupt handler */ |
dd2996b3 JG |
78 | #define INT_TYPE_AI1_INT 1 |
79 | #define INT_TYPE_AI1_DMA 2 | |
80 | #define INT_TYPE_AI3_INT 4 | |
81 | #define INT_TYPE_AI3_DMA 5 | |
82 | #ifdef unused | |
83 | #define INT_TYPE_AI1_DMA_RTC 9 | |
84 | #define INT_TYPE_AI3_DMA_RTC 10 | |
85 | ||
58c0576e | 86 | /* RTC stuff... */ |
dd2996b3 JG |
87 | #define RTC_IRQ 8 |
88 | #define RTC_IO_EXTENT 0x10 | |
89 | #endif | |
90 | ||
91 | #define MAGIC_DMA_WORD 0x5a5a | |
92 | ||
9ced1de6 | 93 | static const struct comedi_lrange range_pcl816 = { 8, { |
dd2996b3 JG |
94 | BIP_RANGE(10), |
95 | BIP_RANGE(5), | |
96 | BIP_RANGE(2.5), | |
97 | BIP_RANGE(1.25), | |
98 | UNI_RANGE(10), | |
99 | UNI_RANGE(5), | |
100 | UNI_RANGE(2.5), | |
101 | UNI_RANGE(1.25), | |
102 | } | |
103 | }; | |
1c7f40d9 BP |
104 | struct pcl816_board { |
105 | ||
58c0576e BP |
106 | const char *name; /* board name */ |
107 | int n_ranges; /* len of range list */ | |
108 | int n_aichan; /* num of A/D chans in diferencial mode */ | |
109 | unsigned int ai_ns_min; /* minimal alllowed delay between samples (in ns) */ | |
110 | int n_aochan; /* num of D/A chans */ | |
111 | int n_dichan; /* num of DI chans */ | |
112 | int n_dochan; /* num of DO chans */ | |
113 | const struct comedi_lrange *ai_range_type; /* default A/D rangelist */ | |
114 | const struct comedi_lrange *ao_range_type; /* dafault D/A rangelist */ | |
115 | unsigned int io_range; /* len of IO space */ | |
116 | unsigned int IRQbits; /* allowed interrupts */ | |
117 | unsigned int DMAbits; /* allowed DMA chans */ | |
118 | int ai_maxdata; /* maxdata for A/D */ | |
119 | int ao_maxdata; /* maxdata for D/A */ | |
120 | int ai_chanlist; /* allowed len of channel list A/D */ | |
121 | int ao_chanlist; /* allowed len of channel list D/A */ | |
122 | int i8254_osc_base; /* 1/frequency of on board oscilator in ns */ | |
1c7f40d9 BP |
123 | }; |
124 | ||
dd2996b3 | 125 | |
1c7f40d9 | 126 | static const struct pcl816_board boardtypes[] = { |
dd2996b3 JG |
127 | {"pcl816", 8, 16, 10000, 1, 16, 16, &range_pcl816, |
128 | &range_pcl816, PCLx1x_RANGE, | |
58c0576e BP |
129 | 0x00fc, /* IRQ mask */ |
130 | 0x0a, /* DMA mask */ | |
131 | 0xffff, /* 16-bit card */ | |
132 | 0xffff, /* D/A maxdata */ | |
dd2996b3 | 133 | 1024, |
58c0576e | 134 | 1, /* ao chan list */ |
dd2996b3 JG |
135 | 100}, |
136 | {"pcl814b", 8, 16, 10000, 1, 16, 16, &range_pcl816, | |
137 | &range_pcl816, PCLx1x_RANGE, | |
138 | 0x00fc, | |
139 | 0x0a, | |
140 | 0x3fff, /* 14 bit card */ | |
141 | 0x3fff, | |
142 | 1024, | |
143 | 1, | |
144 | 100}, | |
145 | }; | |
146 | ||
1c7f40d9 | 147 | #define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl816_board)) |
fe0ff175 | 148 | #define devpriv ((struct pcl816_private *)dev->private) |
1c7f40d9 | 149 | #define this_board ((const struct pcl816_board *)dev->board_ptr) |
dd2996b3 | 150 | |
da91b269 BP |
151 | static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it); |
152 | static int pcl816_detach(struct comedi_device *dev); | |
dd2996b3 JG |
153 | |
154 | #ifdef unused | |
155 | static int RTC_lock = 0; /* RTC lock */ | |
156 | static int RTC_timer_lock = 0; /* RTC int lock */ | |
157 | #endif | |
158 | ||
139dfbdf | 159 | static struct comedi_driver driver_pcl816 = { |
68c3dbff BP |
160 | .driver_name = "pcl816", |
161 | .module = THIS_MODULE, | |
162 | .attach = pcl816_attach, | |
163 | .detach = pcl816_detach, | |
164 | .board_name = &boardtypes[0].name, | |
165 | .num_names = n_boardtypes, | |
166 | .offset = sizeof(struct pcl816_board), | |
dd2996b3 JG |
167 | }; |
168 | ||
169 | COMEDI_INITCLEANUP(driver_pcl816); | |
170 | ||
fe0ff175 BP |
171 | struct pcl816_private { |
172 | ||
58c0576e BP |
173 | unsigned int dma; /* used DMA, 0=don't use DMA */ |
174 | int dma_rtc; /* 1=RTC used with DMA, 0=no RTC alloc */ | |
dd2996b3 | 175 | #ifdef unused |
58c0576e | 176 | unsigned long rtc_iobase; /* RTC port region */ |
dd2996b3 JG |
177 | unsigned int rtc_iosize; |
178 | unsigned int rtc_irq; | |
179 | #endif | |
58c0576e BP |
180 | unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */ |
181 | unsigned int dmapages[2]; /* len of DMA buffers in PAGE_SIZEs */ | |
182 | unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */ | |
183 | unsigned int hwdmasize[2]; /* len of DMA buffers in Bytes */ | |
184 | unsigned int dmasamplsize; /* size in samples hwdmasize[0]/2 */ | |
185 | unsigned int last_top_dma; /* DMA pointer in last RTC int */ | |
186 | int next_dma_buf; /* which DMA buffer will be used next round */ | |
187 | long dma_runs_to_end; /* how many we must permorm DMA transfer to end of record */ | |
188 | unsigned long last_dma_run; /* how many bytes we must transfer on last DMA page */ | |
189 | ||
190 | unsigned int ai_scans; /* len of scanlist */ | |
191 | unsigned char ai_neverending; /* if=1, then we do neverending record (you must use cancel()) */ | |
192 | int irq_free; /* 1=have allocated IRQ */ | |
193 | int irq_blocked; /* 1=IRQ now uses any subdev */ | |
dd2996b3 | 194 | #ifdef unused |
58c0576e | 195 | int rtc_irq_blocked; /* 1=we now do AI with DMA&RTC */ |
dd2996b3 | 196 | #endif |
58c0576e BP |
197 | int irq_was_now_closed; /* when IRQ finish, there's stored int816_mode for last interrupt */ |
198 | int int816_mode; /* who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */ | |
199 | struct comedi_subdevice *last_int_sub; /* ptr to subdevice which now finish */ | |
200 | int ai_act_scan; /* how many scans we finished */ | |
201 | unsigned int ai_act_chanlist[16]; /* MUX setting for actual AI operations */ | |
202 | unsigned int ai_act_chanlist_len; /* how long is actual MUX list */ | |
203 | unsigned int ai_act_chanlist_pos; /* actual position in MUX list */ | |
204 | unsigned int ai_poll_ptr; /* how many sampes transfer poll */ | |
205 | struct comedi_subdevice *sub_ai; /* ptr to AI subdevice */ | |
dd2996b3 | 206 | #ifdef unused |
58c0576e BP |
207 | struct timer_list rtc_irq_timer; /* timer for RTC sanity check */ |
208 | unsigned long rtc_freq; /* RTC int freq */ | |
dd2996b3 | 209 | #endif |
fe0ff175 BP |
210 | }; |
211 | ||
dd2996b3 JG |
212 | |
213 | /* | |
214 | ============================================================================== | |
215 | */ | |
da91b269 BP |
216 | static int check_and_setup_channel_list(struct comedi_device *dev, |
217 | struct comedi_subdevice *s, unsigned int *chanlist, int chanlen); | |
218 | static int pcl816_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s); | |
219 | static void start_pacer(struct comedi_device *dev, int mode, unsigned int divisor1, | |
dd2996b3 JG |
220 | unsigned int divisor2); |
221 | #ifdef unused | |
222 | static int set_rtc_irq_bit(unsigned char bit); | |
223 | #endif | |
224 | ||
da91b269 BP |
225 | static int pcl816_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, |
226 | struct comedi_cmd *cmd); | |
227 | static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); | |
dd2996b3 JG |
228 | |
229 | /* | |
230 | ============================================================================== | |
231 | ANALOG INPUT MODE0, 816 cards, slow version | |
232 | */ | |
da91b269 BP |
233 | static int pcl816_ai_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, |
234 | struct comedi_insn *insn, unsigned int *data) | |
dd2996b3 JG |
235 | { |
236 | int n; | |
237 | int timeout; | |
238 | ||
239 | DPRINTK("mode 0 analog input\n"); | |
58c0576e | 240 | /* software trigger, DMA and INT off */ |
dd2996b3 | 241 | outb(0, dev->iobase + PCL816_CONTROL); |
58c0576e | 242 | /* clear INT (conversion end) flag */ |
dd2996b3 JG |
243 | outb(0, dev->iobase + PCL816_CLRINT); |
244 | ||
58c0576e | 245 | /* Set the input channel */ |
dd2996b3 JG |
246 | outb(CR_CHAN(insn->chanspec) & 0xf, dev->iobase + PCL816_MUX); |
247 | outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE); /* select gain */ | |
248 | ||
249 | for (n = 0; n < insn->n; n++) { | |
250 | ||
251 | outb(0, dev->iobase + PCL816_AD_LO); /* start conversion */ | |
252 | ||
253 | timeout = 100; | |
254 | while (timeout--) { | |
255 | if (!(inb(dev->iobase + PCL816_STATUS) & | |
256 | PCL816_STATUS_DRDY_MASK)) { | |
58c0576e | 257 | /* return read value */ |
dd2996b3 JG |
258 | data[n] = |
259 | ((inb(dev->iobase + | |
260 | PCL816_AD_HI) << 8) | | |
261 | (inb(dev->iobase + PCL816_AD_LO))); | |
262 | ||
263 | outb(0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */ | |
264 | break; | |
265 | } | |
5f74ea14 | 266 | udelay(1); |
dd2996b3 | 267 | } |
58c0576e | 268 | /* Return timeout error */ |
dd2996b3 JG |
269 | if (!timeout) { |
270 | comedi_error(dev, "A/D insn timeout\n"); | |
271 | data[0] = 0; | |
272 | outb(0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */ | |
273 | return -EIO; | |
274 | } | |
275 | ||
276 | } | |
277 | return n; | |
278 | } | |
279 | ||
280 | /* | |
281 | ============================================================================== | |
282 | analog input interrupt mode 1 & 3, 818 cards | |
283 | one sample per interrupt version | |
284 | */ | |
285 | static irqreturn_t interrupt_pcl816_ai_mode13_int(int irq, void *d) | |
286 | { | |
71b5f4f1 | 287 | struct comedi_device *dev = d; |
34c43922 | 288 | struct comedi_subdevice *s = dev->subdevices + 0; |
dd2996b3 JG |
289 | int low, hi; |
290 | int timeout = 50; /* wait max 50us */ | |
291 | ||
292 | while (timeout--) { | |
293 | if (!(inb(dev->iobase + PCL816_STATUS) & | |
294 | PCL816_STATUS_DRDY_MASK)) | |
295 | break; | |
5f74ea14 | 296 | udelay(1); |
dd2996b3 | 297 | } |
58c0576e | 298 | if (!timeout) { /* timeout, bail error */ |
dd2996b3 JG |
299 | outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ |
300 | comedi_error(dev, "A/D mode1/3 IRQ without DRDY!"); | |
301 | pcl816_ai_cancel(dev, s); | |
302 | s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; | |
303 | comedi_event(dev, s); | |
304 | return IRQ_HANDLED; | |
305 | ||
306 | } | |
307 | ||
58c0576e | 308 | /* get the sample */ |
dd2996b3 JG |
309 | low = inb(dev->iobase + PCL816_AD_LO); |
310 | hi = inb(dev->iobase + PCL816_AD_HI); | |
311 | ||
312 | comedi_buf_put(s->async, (hi << 8) | low); | |
313 | ||
314 | outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ | |
315 | ||
316 | if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len) | |
317 | devpriv->ai_act_chanlist_pos = 0; | |
318 | ||
319 | if (s->async->cur_chan == 0) { | |
320 | devpriv->ai_act_scan++; | |
321 | } | |
322 | ||
323 | if (!devpriv->ai_neverending) | |
324 | if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */ | |
325 | /* all data sampled */ | |
326 | pcl816_ai_cancel(dev, s); | |
327 | s->async->events |= COMEDI_CB_EOA; | |
328 | } | |
329 | comedi_event(dev, s); | |
330 | return IRQ_HANDLED; | |
331 | } | |
332 | ||
333 | /* | |
334 | ============================================================================== | |
335 | analog input dma mode 1 & 3, 816 cards | |
336 | */ | |
da91b269 BP |
337 | static void transfer_from_dma_buf(struct comedi_device *dev, struct comedi_subdevice *s, |
338 | short *ptr, unsigned int bufptr, unsigned int len) | |
dd2996b3 JG |
339 | { |
340 | int i; | |
341 | ||
342 | s->async->events = 0; | |
343 | ||
344 | for (i = 0; i < len; i++) { | |
345 | ||
346 | comedi_buf_put(s->async, ptr[bufptr++]); | |
347 | ||
348 | if (++devpriv->ai_act_chanlist_pos >= | |
349 | devpriv->ai_act_chanlist_len) { | |
350 | devpriv->ai_act_chanlist_pos = 0; | |
351 | devpriv->ai_act_scan++; | |
352 | } | |
353 | ||
354 | if (!devpriv->ai_neverending) | |
58c0576e | 355 | if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */ |
dd2996b3 JG |
356 | pcl816_ai_cancel(dev, s); |
357 | s->async->events |= COMEDI_CB_EOA; | |
358 | s->async->events |= COMEDI_CB_BLOCK; | |
359 | break; | |
360 | } | |
361 | } | |
362 | ||
363 | comedi_event(dev, s); | |
364 | } | |
365 | ||
366 | static irqreturn_t interrupt_pcl816_ai_mode13_dma(int irq, void *d) | |
367 | { | |
71b5f4f1 | 368 | struct comedi_device *dev = d; |
34c43922 | 369 | struct comedi_subdevice *s = dev->subdevices + 0; |
dd2996b3 JG |
370 | int len, bufptr, this_dma_buf; |
371 | unsigned long dma_flags; | |
790c5541 | 372 | short *ptr; |
dd2996b3 JG |
373 | |
374 | disable_dma(devpriv->dma); | |
375 | this_dma_buf = devpriv->next_dma_buf; | |
376 | ||
58c0576e | 377 | if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) { /* switch dma bufs */ |
dd2996b3 JG |
378 | |
379 | devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; | |
380 | set_dma_mode(devpriv->dma, DMA_MODE_READ); | |
381 | dma_flags = claim_dma_lock(); | |
58c0576e | 382 | /* clear_dma_ff (devpriv->dma); */ |
dd2996b3 JG |
383 | set_dma_addr(devpriv->dma, |
384 | devpriv->hwdmaptr[devpriv->next_dma_buf]); | |
385 | if (devpriv->dma_runs_to_end) { | |
386 | set_dma_count(devpriv->dma, | |
387 | devpriv->hwdmasize[devpriv->next_dma_buf]); | |
388 | } else { | |
389 | set_dma_count(devpriv->dma, devpriv->last_dma_run); | |
390 | } | |
391 | release_dma_lock(dma_flags); | |
392 | enable_dma(devpriv->dma); | |
393 | } | |
394 | ||
395 | devpriv->dma_runs_to_end--; | |
396 | outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ | |
397 | ||
790c5541 | 398 | ptr = (short *) devpriv->dmabuf[this_dma_buf]; |
dd2996b3 JG |
399 | |
400 | len = (devpriv->hwdmasize[0] >> 1) - devpriv->ai_poll_ptr; | |
401 | bufptr = devpriv->ai_poll_ptr; | |
402 | devpriv->ai_poll_ptr = 0; | |
403 | ||
404 | transfer_from_dma_buf(dev, s, ptr, bufptr, len); | |
405 | return IRQ_HANDLED; | |
406 | } | |
407 | ||
408 | /* | |
409 | ============================================================================== | |
410 | INT procedure | |
411 | */ | |
70265d24 | 412 | static irqreturn_t interrupt_pcl816(int irq, void *d) |
dd2996b3 | 413 | { |
71b5f4f1 | 414 | struct comedi_device *dev = d; |
dd2996b3 JG |
415 | DPRINTK("<I>"); |
416 | ||
417 | if (!dev->attached) { | |
418 | comedi_error(dev, "premature interrupt"); | |
419 | return IRQ_HANDLED; | |
420 | } | |
421 | ||
422 | switch (devpriv->int816_mode) { | |
423 | case INT_TYPE_AI1_DMA: | |
424 | case INT_TYPE_AI3_DMA: | |
425 | return interrupt_pcl816_ai_mode13_dma(irq, d); | |
426 | case INT_TYPE_AI1_INT: | |
427 | case INT_TYPE_AI3_INT: | |
428 | return interrupt_pcl816_ai_mode13_int(irq, d); | |
429 | } | |
430 | ||
431 | outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ | |
432 | if ((!dev->irq) | (!devpriv->irq_free) | (!devpriv->irq_blocked) | | |
433 | (!devpriv->int816_mode)) { | |
434 | if (devpriv->irq_was_now_closed) { | |
435 | devpriv->irq_was_now_closed = 0; | |
58c0576e | 436 | /* comedi_error(dev,"last IRQ.."); */ |
dd2996b3 JG |
437 | return IRQ_HANDLED; |
438 | } | |
439 | comedi_error(dev, "bad IRQ!"); | |
440 | return IRQ_NONE; | |
441 | } | |
442 | comedi_error(dev, "IRQ from unknow source!"); | |
443 | return IRQ_NONE; | |
444 | } | |
445 | ||
446 | /* | |
447 | ============================================================================== | |
448 | COMMAND MODE | |
449 | */ | |
da91b269 | 450 | static void pcl816_cmdtest_out(int e, struct comedi_cmd *cmd) |
dd2996b3 | 451 | { |
5f74ea14 | 452 | printk("pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e, |
dd2996b3 | 453 | cmd->start_src, cmd->scan_begin_src, cmd->convert_src); |
5f74ea14 | 454 | printk("pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e, |
dd2996b3 | 455 | cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg); |
5f74ea14 | 456 | printk("pcl816 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src, |
dd2996b3 | 457 | cmd->scan_end_src); |
5f74ea14 | 458 | printk("pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", e, |
dd2996b3 JG |
459 | cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len); |
460 | } | |
461 | ||
462 | /* | |
463 | ============================================================================== | |
464 | */ | |
da91b269 BP |
465 | static int pcl816_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, |
466 | struct comedi_cmd *cmd) | |
dd2996b3 JG |
467 | { |
468 | int err = 0; | |
469 | int tmp, divisor1, divisor2; | |
470 | ||
5f74ea14 | 471 | DEBUG(printk("pcl816 pcl812_ai_cmdtest\n"); |
dd2996b3 JG |
472 | pcl816_cmdtest_out(-1, cmd);); |
473 | ||
474 | /* step 1: make sure trigger sources are trivially valid */ | |
475 | tmp = cmd->start_src; | |
476 | cmd->start_src &= TRIG_NOW; | |
477 | if (!cmd->start_src || tmp != cmd->start_src) | |
478 | err++; | |
479 | ||
480 | tmp = cmd->scan_begin_src; | |
481 | cmd->scan_begin_src &= TRIG_FOLLOW; | |
482 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
483 | err++; | |
484 | ||
485 | if (!cmd->convert_src & (TRIG_EXT | TRIG_TIMER)) | |
486 | err++; | |
487 | ||
488 | tmp = cmd->scan_end_src; | |
489 | cmd->scan_end_src &= TRIG_COUNT; | |
490 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
491 | err++; | |
492 | ||
493 | tmp = cmd->stop_src; | |
494 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
495 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
496 | err++; | |
497 | ||
498 | if (err) { | |
499 | return 1; | |
500 | } | |
501 | ||
502 | /* step 2: make sure trigger sources are unique and mutually compatible */ | |
503 | ||
504 | if (cmd->start_src != TRIG_NOW) { | |
505 | cmd->start_src = TRIG_NOW; | |
506 | err++; | |
507 | } | |
508 | ||
509 | if (cmd->scan_begin_src != TRIG_FOLLOW) { | |
510 | cmd->scan_begin_src = TRIG_FOLLOW; | |
511 | err++; | |
512 | } | |
513 | ||
514 | if (cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_TIMER) { | |
515 | cmd->convert_src = TRIG_TIMER; | |
516 | err++; | |
517 | } | |
518 | ||
519 | if (cmd->scan_end_src != TRIG_COUNT) { | |
520 | cmd->scan_end_src = TRIG_COUNT; | |
521 | err++; | |
522 | } | |
523 | ||
524 | if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) | |
525 | err++; | |
526 | ||
527 | if (err) { | |
528 | return 2; | |
529 | } | |
530 | ||
531 | /* step 3: make sure arguments are trivially compatible */ | |
532 | if (cmd->start_arg != 0) { | |
533 | cmd->start_arg = 0; | |
534 | err++; | |
535 | } | |
536 | ||
537 | if (cmd->scan_begin_arg != 0) { | |
538 | cmd->scan_begin_arg = 0; | |
539 | err++; | |
540 | } | |
541 | if (cmd->convert_src == TRIG_TIMER) { | |
542 | if (cmd->convert_arg < this_board->ai_ns_min) { | |
543 | cmd->convert_arg = this_board->ai_ns_min; | |
544 | err++; | |
545 | } | |
546 | } else { /* TRIG_EXT */ | |
547 | if (cmd->convert_arg != 0) { | |
548 | cmd->convert_arg = 0; | |
549 | err++; | |
550 | } | |
551 | } | |
552 | ||
553 | if (!cmd->chanlist_len) { | |
554 | cmd->chanlist_len = 1; | |
555 | err++; | |
556 | } | |
557 | if (cmd->chanlist_len > this_board->n_aichan) { | |
558 | cmd->chanlist_len = this_board->n_aichan; | |
559 | err++; | |
560 | } | |
561 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
562 | cmd->scan_end_arg = cmd->chanlist_len; | |
563 | err++; | |
564 | } | |
565 | if (cmd->stop_src == TRIG_COUNT) { | |
566 | if (!cmd->stop_arg) { | |
567 | cmd->stop_arg = 1; | |
568 | err++; | |
569 | } | |
570 | } else { /* TRIG_NONE */ | |
571 | if (cmd->stop_arg != 0) { | |
572 | cmd->stop_arg = 0; | |
573 | err++; | |
574 | } | |
575 | } | |
576 | ||
577 | if (err) { | |
578 | return 3; | |
579 | } | |
580 | ||
581 | /* step 4: fix up any arguments */ | |
582 | if (cmd->convert_src == TRIG_TIMER) { | |
583 | tmp = cmd->convert_arg; | |
584 | i8253_cascade_ns_to_timer(this_board->i8254_osc_base, | |
585 | &divisor1, &divisor2, &cmd->convert_arg, | |
586 | cmd->flags & TRIG_ROUND_MASK); | |
587 | if (cmd->convert_arg < this_board->ai_ns_min) | |
588 | cmd->convert_arg = this_board->ai_ns_min; | |
589 | if (tmp != cmd->convert_arg) | |
590 | err++; | |
591 | } | |
592 | ||
593 | if (err) { | |
594 | return 4; | |
595 | } | |
596 | ||
597 | return 0; | |
598 | } | |
599 | ||
da91b269 | 600 | static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
dd2996b3 JG |
601 | { |
602 | unsigned int divisor1 = 0, divisor2 = 0, dma_flags, bytes, dmairq; | |
ea6d0d4c | 603 | struct comedi_cmd *cmd = &s->async->cmd; |
dd2996b3 JG |
604 | |
605 | if (cmd->start_src != TRIG_NOW) | |
606 | return -EINVAL; | |
607 | if (cmd->scan_begin_src != TRIG_FOLLOW) | |
608 | return -EINVAL; | |
609 | if (cmd->scan_end_src != TRIG_COUNT) | |
610 | return -EINVAL; | |
611 | if (cmd->scan_end_arg != cmd->chanlist_len) | |
612 | return -EINVAL; | |
58c0576e | 613 | /* if(cmd->chanlist_len>MAX_CHANLIST_LEN) return -EINVAL; */ |
dd2996b3 JG |
614 | if (devpriv->irq_blocked) |
615 | return -EBUSY; | |
616 | ||
617 | if (cmd->convert_src == TRIG_TIMER) { | |
618 | if (cmd->convert_arg < this_board->ai_ns_min) | |
619 | cmd->convert_arg = this_board->ai_ns_min; | |
620 | ||
621 | i8253_cascade_ns_to_timer(this_board->i8254_osc_base, &divisor1, | |
622 | &divisor2, &cmd->convert_arg, | |
623 | cmd->flags & TRIG_ROUND_MASK); | |
58c0576e | 624 | if (divisor1 == 1) { /* PCL816 crash if any divisor is set to 1 */ |
dd2996b3 JG |
625 | divisor1 = 2; |
626 | divisor2 /= 2; | |
627 | } | |
628 | if (divisor2 == 1) { | |
629 | divisor2 = 2; | |
630 | divisor1 /= 2; | |
631 | } | |
632 | } | |
633 | ||
58c0576e | 634 | start_pacer(dev, -1, 0, 0); /* stop pacer */ |
dd2996b3 JG |
635 | |
636 | if (!check_and_setup_channel_list(dev, s, cmd->chanlist, | |
637 | cmd->chanlist_len)) | |
638 | return -EINVAL; | |
5f74ea14 | 639 | udelay(1); |
dd2996b3 JG |
640 | |
641 | devpriv->ai_act_scan = 0; | |
642 | s->async->cur_chan = 0; | |
643 | devpriv->irq_blocked = 1; | |
644 | devpriv->ai_poll_ptr = 0; | |
645 | devpriv->irq_was_now_closed = 0; | |
646 | ||
647 | if (cmd->stop_src == TRIG_COUNT) { | |
648 | devpriv->ai_scans = cmd->stop_arg; | |
649 | devpriv->ai_neverending = 0; | |
650 | } else { | |
651 | devpriv->ai_scans = 0; | |
652 | devpriv->ai_neverending = 1; | |
653 | } | |
654 | ||
58c0576e | 655 | if ((cmd->flags & TRIG_WAKE_EOS)) { /* don't we want wake up every scan? */ |
dd2996b3 | 656 | printk("pl816: You wankt WAKE_EOS but I dont want handle it"); |
58c0576e BP |
657 | /* devpriv->ai_eos=1; */ |
658 | /* if (devpriv->ai_n_chan==1) */ | |
659 | /* devpriv->dma=0; // DMA is useless for this situation */ | |
dd2996b3 JG |
660 | } |
661 | ||
662 | if (devpriv->dma) { | |
663 | bytes = devpriv->hwdmasize[0]; | |
664 | if (!devpriv->ai_neverending) { | |
58c0576e BP |
665 | bytes = s->async->cmd.chanlist_len * s->async->cmd.chanlist_len * sizeof(short); /* how many */ |
666 | devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0]; /* how many DMA pages we must fill */ | |
667 | devpriv->last_dma_run = bytes % devpriv->hwdmasize[0]; /* on last dma transfer must be moved */ | |
dd2996b3 JG |
668 | devpriv->dma_runs_to_end--; |
669 | if (devpriv->dma_runs_to_end >= 0) | |
670 | bytes = devpriv->hwdmasize[0]; | |
671 | } else | |
672 | devpriv->dma_runs_to_end = -1; | |
673 | ||
674 | devpriv->next_dma_buf = 0; | |
675 | set_dma_mode(devpriv->dma, DMA_MODE_READ); | |
676 | dma_flags = claim_dma_lock(); | |
677 | clear_dma_ff(devpriv->dma); | |
678 | set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); | |
679 | set_dma_count(devpriv->dma, bytes); | |
680 | release_dma_lock(dma_flags); | |
681 | enable_dma(devpriv->dma); | |
682 | } | |
683 | ||
684 | start_pacer(dev, 1, divisor1, divisor2); | |
685 | dmairq = ((devpriv->dma & 0x3) << 4) | (dev->irq & 0x7); | |
686 | ||
687 | switch (cmd->convert_src) { | |
688 | case TRIG_TIMER: | |
689 | devpriv->int816_mode = INT_TYPE_AI1_DMA; | |
58c0576e BP |
690 | outb(0x32, dev->iobase + PCL816_CONTROL); /* Pacer+IRQ+DMA */ |
691 | outb(dmairq, dev->iobase + PCL816_STATUS); /* write irq and DMA to card */ | |
dd2996b3 JG |
692 | break; |
693 | ||
694 | default: | |
695 | devpriv->int816_mode = INT_TYPE_AI3_DMA; | |
58c0576e BP |
696 | outb(0x34, dev->iobase + PCL816_CONTROL); /* Ext trig+IRQ+DMA */ |
697 | outb(dmairq, dev->iobase + PCL816_STATUS); /* write irq to card */ | |
dd2996b3 JG |
698 | break; |
699 | } | |
700 | ||
701 | DPRINTK("pcl816 END: pcl812_ai_cmd()\n"); | |
702 | return 0; | |
703 | } | |
704 | ||
da91b269 | 705 | static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) |
dd2996b3 JG |
706 | { |
707 | unsigned long flags; | |
708 | unsigned int top1, top2, i; | |
709 | ||
710 | if (!devpriv->dma) | |
58c0576e | 711 | return 0; /* poll is valid only for DMA transfer */ |
dd2996b3 | 712 | |
5f74ea14 | 713 | spin_lock_irqsave(&dev->spinlock, flags); |
dd2996b3 JG |
714 | |
715 | for (i = 0; i < 20; i++) { | |
58c0576e | 716 | top1 = get_dma_residue(devpriv->dma); /* where is now DMA */ |
dd2996b3 JG |
717 | top2 = get_dma_residue(devpriv->dma); |
718 | if (top1 == top2) | |
719 | break; | |
720 | } | |
721 | if (top1 != top2) { | |
5f74ea14 | 722 | spin_unlock_irqrestore(&dev->spinlock, flags); |
dd2996b3 JG |
723 | return 0; |
724 | } | |
725 | ||
58c0576e BP |
726 | top1 = devpriv->hwdmasize[0] - top1; /* where is now DMA in buffer */ |
727 | top1 >>= 1; /* sample position */ | |
dd2996b3 | 728 | top2 = top1 - devpriv->ai_poll_ptr; |
58c0576e | 729 | if (top2 < 1) { /* no new samples */ |
5f74ea14 | 730 | spin_unlock_irqrestore(&dev->spinlock, flags); |
dd2996b3 JG |
731 | return 0; |
732 | } | |
733 | ||
734 | transfer_from_dma_buf(dev, s, | |
790c5541 | 735 | (short *) devpriv->dmabuf[devpriv->next_dma_buf], |
dd2996b3 JG |
736 | devpriv->ai_poll_ptr, top2); |
737 | ||
58c0576e | 738 | devpriv->ai_poll_ptr = top1; /* new buffer position */ |
5f74ea14 | 739 | spin_unlock_irqrestore(&dev->spinlock, flags); |
dd2996b3 JG |
740 | |
741 | return s->async->buf_write_count - s->async->buf_read_count; | |
742 | } | |
743 | ||
744 | /* | |
745 | ============================================================================== | |
746 | cancel any mode 1-4 AI | |
747 | */ | |
da91b269 | 748 | static int pcl816_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) |
dd2996b3 | 749 | { |
5f74ea14 | 750 | /* DEBUG(printk("pcl816_ai_cancel()\n");) */ |
dd2996b3 JG |
751 | |
752 | if (devpriv->irq_blocked > 0) { | |
753 | switch (devpriv->int816_mode) { | |
754 | #ifdef unused | |
755 | case INT_TYPE_AI1_DMA_RTC: | |
756 | case INT_TYPE_AI3_DMA_RTC: | |
58c0576e | 757 | set_rtc_irq_bit(0); /* stop RTC */ |
dd2996b3 JG |
758 | del_timer(&devpriv->rtc_irq_timer); |
759 | #endif | |
760 | case INT_TYPE_AI1_DMA: | |
761 | case INT_TYPE_AI3_DMA: | |
762 | disable_dma(devpriv->dma); | |
763 | case INT_TYPE_AI1_INT: | |
764 | case INT_TYPE_AI3_INT: | |
765 | outb(inb(dev->iobase + PCL816_CONTROL) & 0x73, dev->iobase + PCL816_CONTROL); /* Stop A/D */ | |
5f74ea14 | 766 | udelay(1); |
dd2996b3 JG |
767 | outb(0, dev->iobase + PCL816_CONTROL); /* Stop A/D */ |
768 | outb(0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */ | |
769 | outb(0x70, dev->iobase + PCL816_CTRCTL); | |
770 | outb(0, dev->iobase + PCL816_AD_LO); | |
771 | inb(dev->iobase + PCL816_AD_LO); | |
772 | inb(dev->iobase + PCL816_AD_HI); | |
773 | outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ | |
774 | outb(0, dev->iobase + PCL816_CONTROL); /* Stop A/D */ | |
775 | devpriv->irq_blocked = 0; | |
776 | devpriv->irq_was_now_closed = devpriv->int816_mode; | |
777 | devpriv->int816_mode = 0; | |
778 | devpriv->last_int_sub = s; | |
58c0576e | 779 | /* s->busy = 0; */ |
dd2996b3 JG |
780 | break; |
781 | } | |
782 | } | |
783 | ||
5f74ea14 | 784 | DEBUG(printk("comedi: pcl816_ai_cancel() successful\n"); |
dd2996b3 JG |
785 | ) |
786 | return 0; | |
787 | } | |
788 | ||
789 | /* | |
790 | ============================================================================== | |
791 | chech for PCL816 | |
792 | */ | |
793 | static int pcl816_check(unsigned long iobase) | |
794 | { | |
795 | outb(0x00, iobase + PCL816_MUX); | |
5f74ea14 | 796 | udelay(1); |
dd2996b3 | 797 | if (inb(iobase + PCL816_MUX) != 0x00) |
58c0576e | 798 | return 1; /* there isn't card */ |
dd2996b3 | 799 | outb(0x55, iobase + PCL816_MUX); |
5f74ea14 | 800 | udelay(1); |
dd2996b3 | 801 | if (inb(iobase + PCL816_MUX) != 0x55) |
58c0576e | 802 | return 1; /* there isn't card */ |
dd2996b3 | 803 | outb(0x00, iobase + PCL816_MUX); |
5f74ea14 | 804 | udelay(1); |
dd2996b3 | 805 | outb(0x18, iobase + PCL816_CONTROL); |
5f74ea14 | 806 | udelay(1); |
dd2996b3 | 807 | if (inb(iobase + PCL816_CONTROL) != 0x18) |
58c0576e BP |
808 | return 1; /* there isn't card */ |
809 | return 0; /* ok, card exist */ | |
dd2996b3 JG |
810 | } |
811 | ||
812 | /* | |
813 | ============================================================================== | |
814 | reset whole PCL-816 cards | |
815 | */ | |
da91b269 | 816 | static void pcl816_reset(struct comedi_device *dev) |
dd2996b3 | 817 | { |
58c0576e BP |
818 | /* outb (0, dev->iobase + PCL818_DA_LO); DAC=0V */ |
819 | /* outb (0, dev->iobase + PCL818_DA_HI); */ | |
5f74ea14 | 820 | /* udelay (1); */ |
58c0576e BP |
821 | /* outb (0, dev->iobase + PCL818_DO_HI); DO=$0000 */ |
822 | /* outb (0, dev->iobase + PCL818_DO_LO); */ | |
5f74ea14 | 823 | /* udelay (1); */ |
dd2996b3 JG |
824 | outb(0, dev->iobase + PCL816_CONTROL); |
825 | outb(0, dev->iobase + PCL816_MUX); | |
826 | outb(0, dev->iobase + PCL816_CLRINT); | |
827 | outb(0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */ | |
828 | outb(0x70, dev->iobase + PCL816_CTRCTL); | |
829 | outb(0x30, dev->iobase + PCL816_CTRCTL); | |
830 | outb(0, dev->iobase + PCL816_RANGE); | |
831 | } | |
832 | ||
833 | /* | |
834 | ============================================================================== | |
835 | Start/stop pacer onboard pacer | |
836 | */ | |
837 | static void | |
da91b269 | 838 | start_pacer(struct comedi_device *dev, int mode, unsigned int divisor1, |
dd2996b3 JG |
839 | unsigned int divisor2) |
840 | { | |
841 | outb(0x32, dev->iobase + PCL816_CTRCTL); | |
842 | outb(0xff, dev->iobase + PCL816_CTR0); | |
843 | outb(0x00, dev->iobase + PCL816_CTR0); | |
5f74ea14 | 844 | udelay(1); |
58c0576e BP |
845 | outb(0xb4, dev->iobase + PCL816_CTRCTL); /* set counter 2 as mode 3 */ |
846 | outb(0x74, dev->iobase + PCL816_CTRCTL); /* set counter 1 as mode 3 */ | |
5f74ea14 | 847 | udelay(1); |
dd2996b3 JG |
848 | |
849 | if (mode == 1) { | |
850 | DPRINTK("mode %d, divisor1 %d, divisor2 %d\n", mode, divisor1, | |
851 | divisor2); | |
852 | outb(divisor2 & 0xff, dev->iobase + PCL816_CTR2); | |
853 | outb((divisor2 >> 8) & 0xff, dev->iobase + PCL816_CTR2); | |
854 | outb(divisor1 & 0xff, dev->iobase + PCL816_CTR1); | |
855 | outb((divisor1 >> 8) & 0xff, dev->iobase + PCL816_CTR1); | |
856 | } | |
857 | ||
858 | /* clear pending interrupts (just in case) */ | |
58c0576e | 859 | /* outb(0, dev->iobase + PCL816_CLRINT); */ |
dd2996b3 JG |
860 | } |
861 | ||
862 | /* | |
863 | ============================================================================== | |
864 | Check if channel list from user is builded correctly | |
865 | If it's ok, then program scan/gain logic | |
866 | */ | |
867 | static int | |
da91b269 | 868 | check_and_setup_channel_list(struct comedi_device *dev, struct comedi_subdevice *s, |
dd2996b3 JG |
869 | unsigned int *chanlist, int chanlen) |
870 | { | |
871 | unsigned int chansegment[16]; | |
872 | unsigned int i, nowmustbechan, seglen, segpos; | |
873 | ||
58c0576e | 874 | /* correct channel and range number check itself comedi/range.c */ |
dd2996b3 JG |
875 | if (chanlen < 1) { |
876 | comedi_error(dev, "range/channel list is empty!"); | |
877 | return 0; | |
878 | } | |
879 | ||
880 | if (chanlen > 1) { | |
58c0576e | 881 | chansegment[0] = chanlist[0]; /* first channel is everytime ok */ |
dd2996b3 | 882 | for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { |
58c0576e | 883 | /* build part of chanlist */ |
5f74ea14 | 884 | DEBUG(printk("%d. %d %d\n", i, CR_CHAN(chanlist[i]), |
dd2996b3 JG |
885 | CR_RANGE(chanlist[i])); |
886 | ) | |
887 | if (chanlist[0] == chanlist[i]) | |
58c0576e | 888 | break; /* we detect loop, this must by finish */ |
dd2996b3 JG |
889 | nowmustbechan = |
890 | (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; | |
891 | if (nowmustbechan != CR_CHAN(chanlist[i])) { | |
58c0576e | 892 | /* channel list isn't continous :-( */ |
5f74ea14 | 893 | printk |
dd2996b3 JG |
894 | ("comedi%d: pcl816: channel list must be continous! chanlist[%i]=%d but must be %d or %d!\n", |
895 | dev->minor, i, CR_CHAN(chanlist[i]), | |
896 | nowmustbechan, CR_CHAN(chanlist[0])); | |
897 | return 0; | |
898 | } | |
58c0576e | 899 | chansegment[i] = chanlist[i]; /* well, this is next correct channel in list */ |
dd2996b3 JG |
900 | } |
901 | ||
58c0576e | 902 | for (i = 0, segpos = 0; i < chanlen; i++) { /* check whole chanlist */ |
5f74ea14 | 903 | DEBUG(printk("%d %d=%d %d\n", |
dd2996b3 JG |
904 | CR_CHAN(chansegment[i % seglen]), |
905 | CR_RANGE(chansegment[i % seglen]), | |
906 | CR_CHAN(chanlist[i]), | |
907 | CR_RANGE(chanlist[i])); | |
908 | ) | |
909 | if (chanlist[i] != chansegment[i % seglen]) { | |
5f74ea14 | 910 | printk |
dd2996b3 JG |
911 | ("comedi%d: pcl816: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", |
912 | dev->minor, i, CR_CHAN(chansegment[i]), | |
913 | CR_RANGE(chansegment[i]), | |
914 | CR_AREF(chansegment[i]), | |
915 | CR_CHAN(chanlist[i % seglen]), | |
916 | CR_RANGE(chanlist[i % seglen]), | |
917 | CR_AREF(chansegment[i % seglen])); | |
58c0576e | 918 | return 0; /* chan/gain list is strange */ |
dd2996b3 JG |
919 | } |
920 | } | |
921 | } else { | |
922 | seglen = 1; | |
923 | } | |
924 | ||
925 | devpriv->ai_act_chanlist_len = seglen; | |
926 | devpriv->ai_act_chanlist_pos = 0; | |
927 | ||
58c0576e | 928 | for (i = 0; i < seglen; i++) { /* store range list to card */ |
dd2996b3 JG |
929 | devpriv->ai_act_chanlist[i] = CR_CHAN(chanlist[i]); |
930 | outb(CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX); | |
931 | outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE); /* select gain */ | |
932 | } | |
933 | ||
5f74ea14 | 934 | udelay(1); |
dd2996b3 JG |
935 | |
936 | outb(devpriv->ai_act_chanlist[0] | (devpriv->ai_act_chanlist[seglen - 1] << 4), dev->iobase + PCL816_MUX); /* select channel interval to scan */ | |
937 | ||
58c0576e | 938 | return 1; /* we can serve this with MUX logic */ |
dd2996b3 JG |
939 | } |
940 | ||
941 | #ifdef unused | |
942 | /* | |
943 | ============================================================================== | |
944 | Enable(1)/disable(0) periodic interrupts from RTC | |
945 | */ | |
946 | static int set_rtc_irq_bit(unsigned char bit) | |
947 | { | |
948 | unsigned char val; | |
949 | unsigned long flags; | |
950 | ||
951 | if (bit == 1) { | |
952 | RTC_timer_lock++; | |
953 | if (RTC_timer_lock > 1) | |
954 | return 0; | |
955 | } else { | |
956 | RTC_timer_lock--; | |
957 | if (RTC_timer_lock < 0) | |
958 | RTC_timer_lock = 0; | |
959 | if (RTC_timer_lock > 0) | |
960 | return 0; | |
961 | } | |
962 | ||
963 | save_flags(flags); | |
964 | cli(); | |
965 | val = CMOS_READ(RTC_CONTROL); | |
966 | if (bit) { | |
967 | val |= RTC_PIE; | |
968 | } else { | |
969 | val &= ~RTC_PIE; | |
970 | } | |
971 | CMOS_WRITE(val, RTC_CONTROL); | |
972 | CMOS_READ(RTC_INTR_FLAGS); | |
973 | restore_flags(flags); | |
974 | return 0; | |
975 | } | |
976 | #endif | |
977 | ||
978 | /* | |
979 | ============================================================================== | |
980 | Free any resources that we have claimed | |
981 | */ | |
da91b269 | 982 | static void free_resources(struct comedi_device *dev) |
dd2996b3 | 983 | { |
5f74ea14 | 984 | /* printk("free_resource()\n"); */ |
dd2996b3 JG |
985 | if (dev->private) { |
986 | pcl816_ai_cancel(dev, devpriv->sub_ai); | |
987 | pcl816_reset(dev); | |
988 | if (devpriv->dma) | |
989 | free_dma(devpriv->dma); | |
990 | if (devpriv->dmabuf[0]) | |
991 | free_pages(devpriv->dmabuf[0], devpriv->dmapages[0]); | |
992 | if (devpriv->dmabuf[1]) | |
993 | free_pages(devpriv->dmabuf[1], devpriv->dmapages[1]); | |
994 | #ifdef unused | |
995 | if (devpriv->rtc_irq) | |
5f74ea14 | 996 | free_irq(devpriv->rtc_irq, dev); |
dd2996b3 JG |
997 | if ((devpriv->dma_rtc) && (RTC_lock == 1)) { |
998 | if (devpriv->rtc_iobase) | |
999 | release_region(devpriv->rtc_iobase, | |
1000 | devpriv->rtc_iosize); | |
1001 | } | |
1002 | #endif | |
1003 | } | |
1004 | ||
1005 | if (dev->irq) | |
1006 | free_irq(dev->irq, dev); | |
1007 | if (dev->iobase) | |
1008 | release_region(dev->iobase, this_board->io_range); | |
5f74ea14 | 1009 | /* printk("free_resource() end\n"); */ |
dd2996b3 JG |
1010 | } |
1011 | ||
1012 | /* | |
1013 | ============================================================================== | |
1014 | ||
1015 | Initialization | |
1016 | ||
1017 | */ | |
da91b269 | 1018 | static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
dd2996b3 JG |
1019 | { |
1020 | int ret; | |
1021 | unsigned long iobase; | |
1022 | unsigned int irq, dma; | |
1023 | unsigned long pages; | |
58c0576e | 1024 | /* int i; */ |
34c43922 | 1025 | struct comedi_subdevice *s; |
dd2996b3 JG |
1026 | |
1027 | /* claim our I/O space */ | |
1028 | iobase = it->options[0]; | |
1029 | printk("comedi%d: pcl816: board=%s, ioport=0x%03lx", dev->minor, | |
1030 | this_board->name, iobase); | |
1031 | ||
1032 | if (!request_region(iobase, this_board->io_range, "pcl816")) { | |
5f74ea14 | 1033 | printk("I/O port conflict\n"); |
dd2996b3 JG |
1034 | return -EIO; |
1035 | } | |
1036 | ||
1037 | dev->iobase = iobase; | |
1038 | ||
1039 | if (pcl816_check(iobase)) { | |
5f74ea14 | 1040 | printk(", I cann't detect board. FAIL!\n"); |
dd2996b3 JG |
1041 | return -EIO; |
1042 | } | |
1043 | ||
c3744138 BP |
1044 | ret = alloc_private(dev, sizeof(struct pcl816_private)); |
1045 | if (ret < 0) | |
dd2996b3 JG |
1046 | return ret; /* Can't alloc mem */ |
1047 | ||
1048 | /* set up some name stuff */ | |
1049 | dev->board_name = this_board->name; | |
1050 | ||
1051 | /* grab our IRQ */ | |
1052 | irq = 0; | |
1053 | if (this_board->IRQbits != 0) { /* board support IRQ */ | |
1054 | irq = it->options[1]; | |
1055 | if (irq) { /* we want to use IRQ */ | |
1056 | if (((1 << irq) & this_board->IRQbits) == 0) { | |
5f74ea14 | 1057 | printk |
dd2996b3 JG |
1058 | (", IRQ %u is out of allowed range, DISABLING IT", |
1059 | irq); | |
1060 | irq = 0; /* Bad IRQ */ | |
1061 | } else { | |
5f74ea14 GKH |
1062 | if (request_irq(irq, interrupt_pcl816, 0, "pcl816", dev)) { |
1063 | printk | |
dd2996b3 JG |
1064 | (", unable to allocate IRQ %u, DISABLING IT", |
1065 | irq); | |
1066 | irq = 0; /* Can't use IRQ */ | |
1067 | } else { | |
5f74ea14 | 1068 | printk(", irq=%u", irq); |
dd2996b3 JG |
1069 | } |
1070 | } | |
1071 | } | |
1072 | } | |
1073 | ||
1074 | dev->irq = irq; | |
1075 | if (irq) { | |
1076 | devpriv->irq_free = 1; | |
1077 | } /* 1=we have allocated irq */ | |
1078 | else { | |
1079 | devpriv->irq_free = 0; | |
1080 | } | |
1081 | devpriv->irq_blocked = 0; /* number of subdevice which use IRQ */ | |
1082 | devpriv->int816_mode = 0; /* mode of irq */ | |
1083 | ||
1084 | #ifdef unused | |
1085 | /* grab RTC for DMA operations */ | |
1086 | devpriv->dma_rtc = 0; | |
58c0576e | 1087 | if (it->options[2] > 0) { /* we want to use DMA */ |
dd2996b3 JG |
1088 | if (RTC_lock == 0) { |
1089 | if (!request_region(RTC_PORT(0), RTC_IO_EXTENT, | |
1090 | "pcl816 (RTC)")) | |
1091 | goto no_rtc; | |
1092 | } | |
1093 | devpriv->rtc_iobase = RTC_PORT(0); | |
1094 | devpriv->rtc_iosize = RTC_IO_EXTENT; | |
1095 | RTC_lock++; | |
1096 | #ifdef UNTESTED_CODE | |
5f74ea14 | 1097 | if (!request_irq(RTC_IRQ, interrupt_pcl816_ai_mode13_dma_rtc, 0, |
dd2996b3 JG |
1098 | "pcl816 DMA (RTC)", dev)) { |
1099 | devpriv->dma_rtc = 1; | |
1100 | devpriv->rtc_irq = RTC_IRQ; | |
5f74ea14 | 1101 | printk(", dma_irq=%u", devpriv->rtc_irq); |
dd2996b3 JG |
1102 | } else { |
1103 | RTC_lock--; | |
1104 | if (RTC_lock == 0) { | |
1105 | if (devpriv->rtc_iobase) | |
1106 | release_region(devpriv->rtc_iobase, | |
1107 | devpriv->rtc_iosize); | |
1108 | } | |
1109 | devpriv->rtc_iobase = 0; | |
1110 | devpriv->rtc_iosize = 0; | |
1111 | } | |
1112 | #else | |
1113 | printk("pcl816: RTC code missing"); | |
1114 | #endif | |
1115 | ||
1116 | } | |
1117 | ||
1118 | no_rtc: | |
1119 | #endif | |
1120 | /* grab our DMA */ | |
1121 | dma = 0; | |
1122 | devpriv->dma = dma; | |
1123 | if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0)) | |
1124 | goto no_dma; /* if we haven't IRQ, we can't use DMA */ | |
1125 | ||
1126 | if (this_board->DMAbits != 0) { /* board support DMA */ | |
1127 | dma = it->options[2]; | |
1128 | if (dma < 1) | |
1129 | goto no_dma; /* DMA disabled */ | |
1130 | ||
1131 | if (((1 << dma) & this_board->DMAbits) == 0) { | |
5f74ea14 | 1132 | printk(", DMA is out of allowed range, FAIL!\n"); |
dd2996b3 JG |
1133 | return -EINVAL; /* Bad DMA */ |
1134 | } | |
1135 | ret = request_dma(dma, "pcl816"); | |
1136 | if (ret) { | |
5f74ea14 | 1137 | printk(", unable to allocate DMA %u, FAIL!\n", dma); |
dd2996b3 JG |
1138 | return -EBUSY; /* DMA isn't free */ |
1139 | } | |
1140 | ||
1141 | devpriv->dma = dma; | |
5f74ea14 | 1142 | printk(", dma=%u", dma); |
dd2996b3 JG |
1143 | pages = 2; /* we need 16KB */ |
1144 | devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages); | |
1145 | ||
1146 | if (!devpriv->dmabuf[0]) { | |
5f74ea14 | 1147 | printk(", unable to allocate DMA buffer, FAIL!\n"); |
dd2996b3 JG |
1148 | /* maybe experiment with try_to_free_pages() will help .... */ |
1149 | return -EBUSY; /* no buffer :-( */ | |
1150 | } | |
1151 | devpriv->dmapages[0] = pages; | |
1152 | devpriv->hwdmaptr[0] = virt_to_bus((void *)devpriv->dmabuf[0]); | |
1153 | devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE; | |
5f74ea14 | 1154 | /* printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); */ |
dd2996b3 | 1155 | |
58c0576e | 1156 | if (devpriv->dma_rtc == 0) { /* we must do duble buff :-( */ |
dd2996b3 JG |
1157 | devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages); |
1158 | if (!devpriv->dmabuf[1]) { | |
5f74ea14 | 1159 | printk |
dd2996b3 JG |
1160 | (", unable to allocate DMA buffer, FAIL!\n"); |
1161 | return -EBUSY; | |
1162 | } | |
1163 | devpriv->dmapages[1] = pages; | |
1164 | devpriv->hwdmaptr[1] = | |
1165 | virt_to_bus((void *)devpriv->dmabuf[1]); | |
1166 | devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE; | |
1167 | } | |
1168 | } | |
1169 | ||
1170 | no_dma: | |
1171 | ||
1172 | /* if (this_board->n_aochan > 0) | |
1173 | subdevs[1] = COMEDI_SUBD_AO; | |
1174 | if (this_board->n_dichan > 0) | |
1175 | subdevs[2] = COMEDI_SUBD_DI; | |
1176 | if (this_board->n_dochan > 0) | |
1177 | subdevs[3] = COMEDI_SUBD_DO; | |
1178 | */ | |
c3744138 BP |
1179 | |
1180 | ret = alloc_subdevices(dev, 1); | |
1181 | if (ret < 0) | |
dd2996b3 JG |
1182 | return ret; |
1183 | ||
1184 | s = dev->subdevices + 0; | |
1185 | if (this_board->n_aichan > 0) { | |
1186 | s->type = COMEDI_SUBD_AI; | |
1187 | devpriv->sub_ai = s; | |
1188 | dev->read_subdev = s; | |
1189 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; | |
1190 | s->n_chan = this_board->n_aichan; | |
1191 | s->subdev_flags |= SDF_DIFF; | |
58c0576e | 1192 | /* printk (", %dchans DIFF DAC - %d", s->n_chan, i); */ |
dd2996b3 JG |
1193 | s->maxdata = this_board->ai_maxdata; |
1194 | s->len_chanlist = this_board->ai_chanlist; | |
1195 | s->range_table = this_board->ai_range_type; | |
1196 | s->cancel = pcl816_ai_cancel; | |
1197 | s->do_cmdtest = pcl816_ai_cmdtest; | |
1198 | s->do_cmd = pcl816_ai_cmd; | |
1199 | s->poll = pcl816_ai_poll; | |
1200 | s->insn_read = pcl816_ai_insn_read; | |
1201 | } else { | |
1202 | s->type = COMEDI_SUBD_UNUSED; | |
1203 | } | |
1204 | ||
1205 | #if 0 | |
1206 | case COMEDI_SUBD_AO: | |
1207 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND; | |
1208 | s->n_chan = this_board->n_aochan; | |
1209 | s->maxdata = this_board->ao_maxdata; | |
1210 | s->len_chanlist = this_board->ao_chanlist; | |
1211 | s->range_table = this_board->ao_range_type; | |
1212 | break; | |
1213 | ||
1214 | case COMEDI_SUBD_DI: | |
1215 | s->subdev_flags = SDF_READABLE; | |
1216 | s->n_chan = this_board->n_dichan; | |
1217 | s->maxdata = 1; | |
1218 | s->len_chanlist = this_board->n_dichan; | |
1219 | s->range_table = &range_digital; | |
1220 | break; | |
1221 | ||
1222 | case COMEDI_SUBD_DO: | |
1223 | s->subdev_flags = SDF_WRITABLE; | |
1224 | s->n_chan = this_board->n_dochan; | |
1225 | s->maxdata = 1; | |
1226 | s->len_chanlist = this_board->n_dochan; | |
1227 | s->range_table = &range_digital; | |
1228 | break; | |
1229 | #endif | |
1230 | ||
1231 | pcl816_reset(dev); | |
1232 | ||
5f74ea14 | 1233 | printk("\n"); |
dd2996b3 JG |
1234 | |
1235 | return 0; | |
1236 | } | |
1237 | ||
1238 | /* | |
1239 | ============================================================================== | |
1240 | Removes device | |
1241 | */ | |
da91b269 | 1242 | static int pcl816_detach(struct comedi_device *dev) |
dd2996b3 | 1243 | { |
5f74ea14 | 1244 | DEBUG(printk("comedi%d: pcl816: remove\n", dev->minor); |
dd2996b3 JG |
1245 | ) |
1246 | free_resources(dev); | |
1247 | #ifdef unused | |
1248 | if (devpriv->dma_rtc) | |
1249 | RTC_lock--; | |
1250 | #endif | |
1251 | return 0; | |
1252 | } |