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