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