/
adcboard.c
825 lines (807 loc) · 31.4 KB
/
adcboard.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* This is a device driver for the ACCES I/O Products ADC Converter
* board, Model PCI-AI12-16.
*
* This is free software, written by Richard B. Johnson. It is
* presumed to be correct but it is not warranted to do anything
* useful. Therefore, one must use this at their own peril since
* it is possible for any kernel code to corrupt a machine and
* destroy valuable property existing on storage media.
*
* This software is Copyright(c) 2003, by Richard B. Johnson
* Everybody is granted a non-exclusive license to use this
* software for any purpose. However, you are required to retain
* the author's name within this source-code.
*
* This software is written using publicly-available data, an
* instruction manual downloaded from the vendor's Web Page.
* Additional information was obtained experimentally. Therefore
* it is believed that no proprietary nor trade-secret information
* was used to write this driver.
*
* Please send any bug-fixes or improvements to rjohnson@analogic.com
*
* I have implemented two modes of operation.
*
* (1) The streaming mode where one channel is used to
* acquire data as rapidly as possible. The conversion is timed
* by the internal timers so that there is a known sample-rate
* that can be used for further data processing.
*
* (2) The multiplexing mode where all channels are converted in
* a round-robin fashion. The data from the channels are filtered
* using an IIR filter, and the data may be read at any time without
* upsetting the conversion process. The filtered data are read
* from memory without affecting the conversion process.
*
* This is the mode one would normally use to acquire noisy data
* in an industrial environment.
*
* In both cases, the sample-rate can be set from user-mode via
* ioctl(), and, in the case of the multiplexing mode, the IIR
* filter coefficient may be set as well.
*
* Note that this will only run on an ix86 platform, not the
* other Linux ones like Sparc, Power/PC, etc.
*
*/
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/pci.h>
#include <asm/io.h>
#include "adcboard.h"
#define ADC_CLASS 0xff00
#define ADC_DEV 0xaca8
#define ADC_DEVA 0xaca9
#define ADC_VENDOR 0x494f
#define TRUE 1
#define NoDEBUG
#ifdef DEBUG
#define DEB(f) f
#else
#define DEB(f)
#endif
static const char devname[]="ACCES I/O PRODUCTS PCI-AI12-16";
static const char vers[]="V1.1";
MODULE_AUTHOR("Richard B. Johnson");
MODULE_DESCRIPTION("Driver for PCI-AI12-16(A) A/D converter board");
MODULE_SUPPORTED_DEVICE("PCI-AI12-16(A)");
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Macros. These are supposed to reduce code clutter.
*/
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* The ADC Control registers.
*/
#define ADC_STAR 0x0000
#define ADC_CONT 0x0002
#define ADC_DIGI 0x0003
#define ADC_OPTC 0x0004
#define ADC_GPOT 0x0005
#define ADC_TIM0 0x0008
#define ADC_TIM1 0x0009
#define ADC_TIM2 0x000a
#define ADC_TIMC 0x000b
#define ADC_DATA 0x0000
#define ADC_CRDB 0x0002
#define ADC_ORDB 0x0003
#define ADC_STAT 0x0004
#define ADC_GPIN 0x0005
#define ADC_R0 0x01
#define ADC_R1 0x02
#define ADC_R2 0x04
#define ADC_MA0 0x10
#define ADC_MA1 0x20
#define ADC_MA2 0x40
#define ADC_MA3 0x80
#define ADC_CTR 0x01
#define ADC_EI 0x04
#define ADC_EIFH 0x04
#define ADC_XSCE 0x08
#define ADC_EXT 0x01
#define ADC_CF 0x08
#define ADC_CCF 0x40
#define ADC_FE 0x02
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
#define BUF_LEN 0x1000
#define BUF_MSK (BUF_LEN -1)
#define DIVISOR 100 /* Default timer divisor */
#define CTL_LEN 0x10 /* Length of I/O space */
#define NR_CHAN 0x10 /* Number of A/D Channels */
#define INTERRUPT_TIMEOUT (1 * HZ)
#define PCI_INDEX(v) (PCI_BASE_ADDRESS_0 + ((v) * sizeof(long)))
#define ArraySize(a) (sizeof(a) / sizeof(a[0]))
typedef struct {
union {
struct adc_multi ch; /* Individual channel data */
int val[NR_CHAN]; /* Same, but in an array */
} var; /* For ADC variables */
int mod[NR_CHAN]; /* Saved modulus from divide */
int filter[NR_CHAN]; /* Filter coefficients */
} ADC;
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* This is the device-specific information and data storage. There is
* only one global data element, a pointer which points to this structure.
*/
typedef struct {
int buf[BUF_LEN]; /* ADC Data buffer */
int out[BUF_LEN]; /* ADC Data buffer */
char dev[0x30]; /* Device name */
ADC adc; /* ADC filtered data */
struct pci_dev *pci; /* Save to shut down */
spinlock_t adc_lock; /* Spin-lock variable */
wait_queue_head_t wait; /* For poll (select) */
int has_fifo; /* TRUE if it has a FIFO */
int head; /* Circular buffer index */
int tail; /* Circular buffer index */
int major; /* Device major number */
int irq; /* Interrupt to use */
atomic_t unload; /* Unload flag */
atomic_t busy; /* Busy flag */
u32 iolen; /* Length of our window */
size_t adc_chan; /* Input channel in use */
size_t use_chan; /* Input channel to use */
unsigned int inters; /* Number of intrerrupts */
unsigned int poll_mask; /* For select() (poll) */
unsigned int base; /* PCI/Bus IO address */
unsigned int conv_mode; /* Conversion mode */
unsigned int acq_mode; /* Data acquisition mode */
unsigned int timer; /* ADC timer divisor */
} INFO;
static INFO *info;
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Function prototypes.
*/
int __init init_module(void);
void cleanup_module(void);
static int adc_close(struct inode *inp, struct file *fp);
static int adc_open(struct inode *inp, struct file *fp);
static int adc_read(struct file *fp, char *cp, size_t len, loff_t *ppos);
static int adc_ioctl(struct inode *inp, struct file *fp, unsigned int cmd, unsigned long ptr);
static unsigned int adc_poll(struct file *fp, struct poll_table_struct *pt);
static size_t next_chan(INFO *inf);
static irqreturn_t adc_isr(int irq, void *lcl);
static void acq_data(INFO *inf);
static void set_mode(INFO *inf);
static void stop_board(INFO *inf);
static void ctr_mode(unsigned int addr, unsigned int cntr, unsigned int mode);
static void ctr_load(unsigned int addr, unsigned int cntr, unsigned int val);
static void set_point_list(INFO *inf);
static int remote_set_point_list(INFO *inf, struct adc_list *sdc);
static void set_all_filters(INFO *inf, int val);
static void clear_all_data(INFO *inf);
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* File operation structure contains function pointers to various routines.
*/
static struct file_operations adc_fops = {
owner : THIS_MODULE ,
read : adc_read ,
poll : adc_poll ,
ioctl : adc_ioctl ,
open : adc_open ,
release : adc_close ,
get_unmapped_area : NULL };
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Set the filter values for all the channels at once.
*/
static void set_all_filters(INFO *inf, int val)
{
size_t i;
for(i=0; i< NR_CHAN; i++)
inf->adc.filter[i] = val;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* clear all the channel data at once.
*/
static void clear_all_data(INFO *inf)
{
memset(inf->adc.var.val, 0x00, sizeof(inf->adc.var.val));
memset(inf->adc.mod, 0x00, sizeof(inf->adc.mod));
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Set a counter mode.
*/
static void ctr_mode(unsigned int addr, unsigned int cntr, unsigned int mode)
{
outb((cntr << 6) | 0x30 | (mode << 1), addr + ADC_TIMC);
return;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Load a counter divisor.
*/
static void ctr_load(unsigned int addr, unsigned int cntr, unsigned int val)
{
outb( val & 0xff , addr + cntr + ADC_TIM0);
outb((val >> 0x08) & 0xff, addr + cntr + ADC_TIM0);
return;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* This returns the current input channel and sets the next one
* so conversion can proceed at any time.
*/
static size_t next_chan(INFO *inf)
{
size_t chan;
chan = inf->adc_chan++; /* Channel when acquired */
inf->adc_chan &= 0x0f; /* Auto-wrap */
outb(inf->conv_mode | (inf->adc_chan << 4), inf->base + ADC_CONT);
return chan;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Acquire and/or filter the data. Note that this is called under a
* spin-lock.
*/
#define IIR_FILTER \
old = inf->adc.var.val[chan]; /* Get previous value */\
sav = new; /* Save new value */\
new = old + (inf->adc.mod[chan] + new - old) / inf->adc.filter[chan];\
inf->adc.mod[chan] = (inf->adc.mod[chan] + sav - old) % inf->adc.filter[chan];\
inf->adc.var.val[chan] = new
static void acq_data(INFO *inf)
{
int new, old, sav;
size_t chan;
switch(inf->acq_mode)
{
case ADC_SINGLE_CHAN:
do {
new = inw(inf->base + ADC_DATA); /* Get the converter data */
new <<= 0x14; /* Sign-extend this */
new >>= 0x14;
sav = inf->head; /* Last buffer position */
inf->buf[sav++] = new; /* Put data into the buffer */
sav &= BUF_MSK; /* Auto-wrap around buffer */
if(sav != inf->tail) /* If there room in buffer */
inf->head = sav; /* Is room in the buffer */
} while (inf->has_fifo && inb(inf->base + ADC_STAT) & ADC_FE);
break;
case ADC_MULTI_CHAN:
if(inf->has_fifo)
{
while(inb(inf->base + ADC_STAT) & ADC_FE)
{
new = inw(inf->base+ADC_DATA); /* Get the converter data */
chan = new >> 0x0c; /* Top 4 bits show channel */
new <<= 0x14; /* Sign-extend this */
new >>= 0x04; /* Leave 65536 times larger */
IIR_FILTER; /* Filter and save the data */
}
}
else
{
chan = next_chan(inf); /* Channel when acquired */
new = inw(inf->base + ADC_DATA); /* Get the converter data */
new <<= 0x14; /* Sign-extend this */
new >>= 0x04; /* Leave 65536 times larger */
IIR_FILTER; /* Filter and save the data */
}
break;
default:
printk(KERN_ALERT"%s : Unknown acquisition mode (%d)\n",
inf->dev, inf->acq_mode);
break;
}
(void) inb(inf->base + ADC_STAT); /* Clear interrupt */
return;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* This sets up the point-list for the FIFO. It is only used
* by the boards that have a FIFO.
*/
static void set_point_list(INFO *inf)
{
size_t i;
unsigned short list;
outb(ADC_CF|ADC_CCF, inf->base + ADC_OPTC); /* Reset (empty) FIFO */
outb(0, inf->base + ADC_OPTC); /* Now clear to begin */
for(i=0; i< 2048; i++)
{
if(inf->acq_mode == ADC_SINGLE_CHAN)
list = inf->conv_mode |
(inf->use_chan << 4) |
(inf->use_chan << 12);
else
list = inf->conv_mode |
((i % 0x10) << 4) |
((i % 0x10) << 12);
outw(list, inf->base + ADC_CONT);
}
(void)inw(inf->base + ADC_CONT);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* This sets up the point-list for the FIFO. It is only used
* by the boards that have a FIFO.
*/
static int remote_set_point_list(INFO *inf, struct adc_list *adc)
{
size_t i;
unsigned long flags;
unsigned short list;
if(!inf->has_fifo)
return -EINVAL;
for(i=0; i< NR_CHAN; i++)
if(!adc->list[i].filter) /* Check for div/zero */
return -EINVAL; /* NotGood(tm) */
outb(0, inf->base + ADC_OPTC); /* Clear, stop interrupt */
outb(0, inf->base + ADC_CONT); /* Set input mode */
spin_lock_irqsave(&inf->adc_lock, flags);
for(i=0; i< NR_CHAN; i++)
inf->adc.filter[i] = adc->list[i].filter; /* Set all the filters */
outb(ADC_CF|ADC_CCF, inf->base + ADC_OPTC); /* Reset (empty) FIFO */
outb(0, inf->base + ADC_OPTC); /* Now clear to begin */
for(i=0; i< 2048; i++)
{
if(inf->acq_mode == ADC_SINGLE_CHAN)
list = (adc->list[i % 0x10].input & 0x07) |
(inf->use_chan << 4) |
(inf->use_chan << 12);
else
list = (adc->list[i % 0x10].input & 0x07) |
((i % 0x10) << 4) |
((i % 0x10) << 12);
outw(list, inf->base + ADC_CONT);
}
(void)inw(inf->base + ADC_CONT); /* Read and throw away */
outb(ADC_CTR|ADC_EIFH, inf->base + ADC_OPTC); /* Set interrupt/timer */
inf->head = inf->tail = 0; /* Clear interrupt buffer */
clear_all_data(inf); /* Clear filter array */
inf->poll_mask = 0; /* Clear poll mask */
wake_up_interruptible(&inf->wait); /* No data are avaiable */
spin_unlock_irqrestore(&inf->adc_lock, flags);
return 0;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Set the conversion mode for the device. This is not called under
* a lock. Therefore, the board must be stopped first before calling
* this. This will restart the board.
*/
static void set_mode(INFO *inf)
{
int count;
unsigned long flags;
stop_board(inf);
count = 2;
while((inf->timer/count) > 65534) /* Last even count */
count <<=1;
count |= (inf->timer & 1); /* In case it's odd */
DEB(printk("Timer value = %d\n", inf->timer));
DEB(printk("Count value = %d\n", count));
spin_lock_irqsave(&inf->adc_lock, flags);
inf->adc_chan = inf->use_chan;
switch(inf->acq_mode)
{
case ADC_SINGLE_CHAN: /* Acquire streaming data from chan 0 */
ctr_mode(inf->base, 0, 2); /* Counter 0 to square-wave (2) */
ctr_mode(inf->base, 1, 2); /* Counter 1 to square-wave (2) */
ctr_mode(inf->base, 2, 2); /* Counter 2 to square-wave (2) */
ctr_load(inf->base, 0, 0xffff); /* Counter 0 divisor */
ctr_load(inf->base, 1, count); /* Counter 1 divisor */
ctr_load(inf->base, 2, inf->timer/count); /* Counter 2 divisor */
if(inf->has_fifo)
{
set_point_list(inf);
outb(ADC_CTR|ADC_EIFH, inf->base + ADC_OPTC);
}
else
{
outb(inf->conv_mode | (inf->adc_chan << 4), inf->base + ADC_CONT);
outb(ADC_CTR|ADC_EI, inf->base + ADC_OPTC);
}
break;
case ADC_MULTI_CHAN: /* Acquire filtered data from all */
ctr_mode(inf->base, 0, 2); /* Counter 0 to square-wave (2) */
ctr_mode(inf->base, 1, 2); /* Counter 1 to square-wave (2) */
ctr_mode(inf->base, 2, 2); /* Counter 2 to square-wave (2) */
ctr_load(inf->base, 0, 0xffff); /* Counter 0 divisor */
ctr_load(inf->base, 1, count); /* Counter 1 divisor */
ctr_load(inf->base, 2, inf->timer/count); /* Counter 2 divisor */
if(inf->has_fifo)
{
set_point_list(inf);
outb(ADC_CTR|ADC_EIFH, inf->base + ADC_OPTC);
}
else
{
outb(inf->conv_mode | (inf->adc_chan << 4), inf->base + ADC_CONT);
outb(ADC_CTR|ADC_EI, inf->base + ADC_OPTC);
}
break;
default:
printk(KERN_ALERT"%s : Unknown acquisition mode (%d)\n",
inf->dev, inf->acq_mode);
break;
}
inf->head = inf->tail = 0; /* Clear interrupt buffer */
clear_all_data(inf); /* Clear filter array */
inf->poll_mask = 0; /* Clear poll mask */
wake_up_interruptible(&inf->wait); /* No data are avaiable */
spin_unlock_irqrestore(&inf->adc_lock, flags);
return;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* This stops the board from converting and interrupting.
*/
static void stop_board(INFO *inf)
{
outb(0, inf->base + ADC_OPTC); /* Clear, stop interrupt */
outb(0, inf->base + ADC_CONT); /* Set input mode */
inf->head = inf->tail = 0; /* Clear interrupt buffer */
return;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Device poll. We execute a wake-up from the ISR everytime data
* are available. Therefore, there is a spin-lock around the poll
* mask change.
*/
static unsigned int adc_poll(struct file *fp, struct poll_table_struct *pt)
{
unsigned long flags;
unsigned int mask;
poll_wait(fp, &info->wait, pt);
spin_lock_irqsave(&info->adc_lock, flags);
mask = info->poll_mask;
info->poll_mask = 0;
spin_unlock_irqrestore(&info->adc_lock, flags);
return mask;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* For device control. We set various modes and parameters from here
* and also grab a copy of the filtered channel data.
*/
static int adc_ioctl(struct inode *inp, struct file *fp, unsigned int cmd,
unsigned long ptr)
{
size_t i;
unsigned long flags;
int tmp;
struct adc_list *adc;
switch(cmd)
{
case ADC_SINGLE_CHAN: /* Acquire streaming data from chan 0 */
case ADC_MULTI_CHAN: /* Acquire filtered data from all */
info->acq_mode = cmd; /* Set data acquisition mode */
set_mode(info); /* Set up the board */
break;
case ADC_READ_MULTI: /* Read all filtered data */
/*
* Here I copy the filtered data under a lock from the filter buffer
* to an output buffer. In the process, I divide the data by 65536
* to normalize it because it was filtered at a fixed-point multiplier
* of the same value.
*/
spin_lock_irqsave(&info->adc_lock, flags);
for(i=0; i< ArraySize(info->adc.var.val); i++)
info->out[i] = info->adc.var.val[i] >> 0x10;
spin_unlock_irqrestore(&info->adc_lock, flags);
/*
* Release the lock, then copy to the user.
*/
if(copy_to_user((char *)ptr, info->out, sizeof(struct adc_multi)))
return -EFAULT;
break;
case ADC_SET_FILTER:
if(get_user(tmp, (int *)ptr))
return -EFAULT;
if(tmp <= 0) return -EINVAL; /* Bad user input value */
set_all_filters(info, tmp); /* Okay, set the filters */
break;
case ADC_SET_TIMER:
if(get_user(tmp, (int *)ptr))
return -EFAULT;
if(tmp <= 0)
return -EINVAL;
info->timer = tmp;
set_mode(info); /* Restart with new parameters */
break;
case ADC_SET_CHAN: /* Set a new input channel */
if(get_user(tmp, (int *)ptr))
return -EFAULT;
if((tmp < 0) || (tmp > 0x0F))
return -EINVAL;
info->use_chan = tmp;
set_mode(info); /* Restart with new parameters */
break;
case ADC_DIAGS:
info->use_chan = 0;
info->acq_mode = ADC_SINGLE_CHAN; /* Data acquisition mode */
info->timer = 100; /* Small counter value */
info->conv_mode = ADC_R0|ADC_R1; /* +/- 2.5 volts, chan 0 */
set_mode(info);
for(i=0; i< 0x10; i++)
((char *)info->out)[i] = (char)inb(info->base + i);
if(copy_to_user((char *)ptr, info->out, 0x10))
return -EFAULT;
break;
case ADC_POINT_LIST:
adc = (struct adc_list *) info->out;
if(copy_from_user(info->out, (char *)ptr, sizeof(struct adc_list)))
return -EFAULT;
return remote_set_point_list(info, adc);
case ADC_GET_INTRS:
if(put_user(info->inters, (int *)ptr))
return -EFAULT;
break;
case ADC_BIP_10: /* Biploar -10V to + 10V */
info->conv_mode = 0;
set_mode(info);
break;
case ADC_BIP_05: /* Bipolar -5V to +5V */
info->conv_mode = ADC_R0;
set_mode(info);
break;
case ADC_BIP_2d5: /* Bipolar -2.5V to + 2.5V */
info->conv_mode = ADC_R1;
set_mode(info);
break;
case ADC_BIP_1d25: /* Bipolar -1.25 to + 1.25V */
info->conv_mode = ADC_R0|ADC_R1;
set_mode(info);
break;
case ADC_UNI_10: /* Unipolar 0 to 10V */
info->conv_mode = ADC_R2;
set_mode(info);
break;
case ADC_UNI_05: /* Unipolar 0 to 5V */
info->conv_mode = ADC_R2|ADC_R0;
set_mode(info);
break;
case ADC_UNI_1d25: /* Unipolar 1.25 to 3.75 V */
info->conv_mode = ADC_R2|ADC_R1;
set_mode(info);
break;
case ADC_UNI_6d25: /* Unipolar 1.25 to 6.25 V */
info->conv_mode = ADC_R2|ADC_R1|ADC_R0;
set_mode(info);
break;
default:
return -EINVAL;
}
return 0;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Read data from the device. Note that the input and output lengths
* are in bytes, although the data are in 4-byte integers. The byte-
* lengths are in keeping with the de facto Unix standard so sizeof()
* can be used from the API.
*/
static int adc_read(struct file *fp, char *cp, size_t len, loff_t *ppos)
{
unsigned long flags;
ssize_t i = 0;
len /= sizeof(int); /* Make word-length */
spin_lock_irqsave(&info->adc_lock, flags);
while((info->head != (info->tail &= BUF_MSK)) && (len--))
info->out[i++] = info->buf[info->tail++];
spin_unlock_irqrestore(&info->adc_lock, flags);
if((i *= sizeof(int) )) /* Make byte-count */
{
if(copy_to_user(cp, info->out, i))
return -EFAULT;
}
return i;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Open the device.
*/
static int adc_open(struct inode *inp, struct file *fp)
{
atomic_inc(&info->busy); /* Show it's busy now */
if(atomic_read(&info->unload)) /* Is it being unloaded */
{
atomic_dec(&info->busy); /* Bump the busy flag */
return -ENODEV; /* It's being unloaded */
}
// MOD_INC_USE_COUNT; /* Bump usage count */
if(atomic_read(&info->busy) == 1) /* First user open */
set_mode(info);
return 0;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Close the device.
*/
static int adc_close(struct inode *inp, struct file *fp)
{
// MOD_DEC_USE_COUNT; /* Decrement usage count */
atomic_dec(&info->busy);
if(!atomic_read(&info->busy)) /* If the last user */
stop_board(info); /* Stop interrupts */
return 0;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Simple ADC interrupt service routine.
*/
static irqreturn_t adc_isr(int irq, void *lcl)
{
spin_lock(&info->adc_lock);
acq_data(info); /* Get the data */
info->poll_mask = POLLIN|POLLRDNORM; /* Set poll mask */
spin_unlock(&info->adc_lock);
info->inters++; /* Show we got an interrupt */
wake_up_interruptible(&info->wait); /* Show data are avaiable */
return IRQ_HANDLED;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Initialize and register the module. The device is on a PCI bridge.
* FYI, there are two `gotos` in this code. If goto freaks send me
* patches to remove them, they are wasting their time.
*/
int __init init_module()
{
int result;
size_t i;
struct pci_dev *pdev = NULL;
u32 ioport;
u32 iolen;
unsigned long timer;
while ((pdev = pci_get_class(ADC_CLASS << 8, pdev)))
{
DEB(printk("Found %08x (%08x)\n", pdev->vendor, pdev->device));
if((pdev->vendor == ADC_VENDOR) &&
((pdev->device == ADC_DEV) || (pdev->device == ADC_DEVA)) )
goto dev_found;
}
printk(KERN_INFO"%s: PCI device not found\n", devname);
return -ENODEV;
dev_found: pci_disable_device(pdev); /* Disable for now */
/*
* Okay, we found the device. But all is not well in San Diego.
* This PCI interface chip is used for a lot of stuff. The first
* I/O address is NOT the one we use to actually talk to the
* chip! We need to search for the I/O address that is 16 bytes
* wide. That's the correct one.
*/
for(i = 0; i< 6; i++) /* Max base addresses */
{
ioport = 0L;
pci_read_config_dword(pdev, PCI_INDEX(i), &ioport);
if(!(ioport & PCI_BASE_ADDRESS_SPACE_IO))
continue;
ioport &= ~PCI_BASE_ADDRESS_SPACE_IO; /* Clear that bit */
/*
* Set all bits in the address decode register and read the result.
*/
iolen = 0xffffffff;
pci_write_config_dword(pdev, PCI_INDEX(i), iolen);
pci_read_config_dword(pdev, PCI_INDEX(i), &iolen);
/*
* Write the original address decode bits back.
*/
pci_write_config_dword(pdev, PCI_INDEX(i), ioport);
iolen = (~iolen + 2) & 0xfffe; /* Required window size */
DEB(printk("iolen = %04x\n", iolen));
if(iolen == CTL_LEN) goto addr_found;
}
printk(KERN_INFO"%s: No I/O space for ADC board\n", devname);
return -ENODEV;
addr_found: result = pci_enable_device(pdev); /* Now enable the device */
if((info = (INFO *) kmalloc(sizeof(INFO), GFP_KERNEL)) == NULL)
{
printk(KERN_CRIT"%s: Can't allocate memory\n", devname);
pci_disable_device(pdev); /* Disable the device */
pci_dev_put(pdev);
return -ENOMEM;
}
memset(info, 0x00, sizeof(INFO)); /* Clear the structure */
strcpy(info->dev, devname);
if(pdev->device == ADC_DEVA)
{
info->has_fifo = TRUE;
strcat(info->dev, " A");
}
info->pci = pdev;
info->major = DEV_MAJOR;
info->adc_lock = SPIN_LOCK_UNLOCKED;
info->irq = pdev->irq;
info->base = ioport;
info->iolen = iolen;
info->conv_mode = ADC_R0|ADC_R1; /* +/- 1.25 volts, chan 0 */
info->timer = DIVISOR;
info->acq_mode = ADC_MULTI_CHAN;
atomic_inc(&info->busy);
atomic_inc(&info->unload);
set_all_filters(info, FILT_COEF);
stop_board(info); /* No interrupts for now */
init_waitqueue_head(&info->wait); /* Set up wait queue */
if((result = register_chrdev(info->major, info->dev, &adc_fops)) < 0)
{
printk(KERN_ALERT"%s: Can't register major number %d\n",
devname, info->major);
pci_disable_device(pdev); /* Disable the device */
pci_dev_put(pdev);
kfree(info);
return result;
}
request_region(info->base, info->iolen, info->dev);
if((result=request_irq(info->irq, adc_isr, SA_SHIRQ, info->dev, info)) < 0)
{
printk(KERN_ALERT"%s: Can't allocate IRQ %d\n", devname, info->irq);
pci_disable_device(pdev); /* Disable the device */
pci_dev_put(pdev);
(void)unregister_chrdev(info->major, info->dev);
release_region(info->base, info->iolen);
kfree(info);
return result;
}
set_mode(info); /* Start the board */
/*
* Let the auto multi-channel mode run for a bit to see if
* the board interrupts okay.
*/
timer = jiffies + INTERRUPT_TIMEOUT;
while(time_before(jiffies, timer))
{
if(info->inters) break;
schedule_timeout(2);
}
stop_board(info); /* Stop the board */
printk(KERN_INFO"%s: Version = %s\n", info->dev, vers);
printk(KERN_INFO"%s: I/O port = %04X\n", info->dev, info->base);
printk(KERN_INFO"%s: I/O length = %d\n", info->dev, info->iolen);
printk(KERN_INFO"%s: Interrupt = %d\n", info->dev, info->irq);
printk(KERN_INFO"%s: Dev. major = %d\n", info->dev, info->major);
printk(KERN_INFO"%s: Patches to rjohnson@analogic.com\n", info->dev);
if(!info->inters)
printk(KERN_ALERT"%s: Warning, no interrupt detected\n", info->dev);
printk(KERN_INFO"%s: Initialization complete\n", info->dev);
atomic_dec(&info->busy);
atomic_dec(&info->unload);
return 0;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* Release the module.
*/
void cleanup_module()
{
atomic_inc(&info->unload);
if(atomic_read(&info->busy))
{
atomic_dec(&info->unload);
printk(KERN_ALERT "%s: Can't unregister, busy.\n", info->dev);
return;
}
stop_board(info); /* Stop the board */
pci_disable_device(info->pci); /* Disable the device */
unregister_chrdev(info->major, info->dev);
free_irq(info->irq, info);
release_region(info->base, info->iolen);
pci_dev_put(info->pci);
printk(KERN_INFO"%s: Module removed\n", info->dev);
kfree(info);
return;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/