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