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