summaryrefslogtreecommitdiff
path: root/drivers/staging/comedi
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/staging/comedi
Initial import
Diffstat (limited to 'drivers/staging/comedi')
-rw-r--r--drivers/staging/comedi/Kconfig1306
-rw-r--r--drivers/staging/comedi/Makefile15
-rw-r--r--drivers/staging/comedi/TODO11
-rw-r--r--drivers/staging/comedi/comedi.h937
-rw-r--r--drivers/staging/comedi/comedi_buf.c550
-rw-r--r--drivers/staging/comedi/comedi_compat32.c463
-rw-r--r--drivers/staging/comedi/comedi_compat32.h36
-rw-r--r--drivers/staging/comedi/comedi_fops.c2921
-rw-r--r--drivers/staging/comedi/comedi_internal.h67
-rw-r--r--drivers/staging/comedi/comedi_pci.c185
-rw-r--r--drivers/staging/comedi/comedi_pci.h64
-rw-r--r--drivers/staging/comedi/comedi_pcmcia.c169
-rw-r--r--drivers/staging/comedi/comedi_pcmcia.h55
-rw-r--r--drivers/staging/comedi/comedi_usb.c131
-rw-r--r--drivers/staging/comedi/comedi_usb.h50
-rw-r--r--drivers/staging/comedi/comedidev.h635
-rw-r--r--drivers/staging/comedi/comedilib.h35
-rw-r--r--drivers/staging/comedi/drivers.c956
-rw-r--r--drivers/staging/comedi/drivers/8255.c342
-rw-r--r--drivers/staging/comedi/drivers/8255.h48
-rw-r--r--drivers/staging/comedi/drivers/8255_pci.c304
-rw-r--r--drivers/staging/comedi/drivers/Makefile145
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c204
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c173
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1032.c394
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1500.c878
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1516.c225
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1564.c593
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_16xx.c187
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_2032.c339
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_2200.c152
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_3120.c1129
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_3501.c448
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_3xxx.c968
-rw-r--r--drivers/staging/comedi/drivers/addi_tcw.h56
-rw-r--r--drivers/staging/comedi/drivers/addi_watchdog.c149
-rw-r--r--drivers/staging/comedi/drivers/addi_watchdog.h9
-rw-r--r--drivers/staging/comedi/drivers/adl_pci6208.c211
-rw-r--r--drivers/staging/comedi/drivers/adl_pci7x3x.c275
-rw-r--r--drivers/staging/comedi/drivers/adl_pci8164.c163
-rw-r--r--drivers/staging/comedi/drivers/adl_pci9111.c775
-rw-r--r--drivers/staging/comedi/drivers/adl_pci9118.c1761
-rw-r--r--drivers/staging/comedi/drivers/adq12b.c268
-rw-r--r--drivers/staging/comedi/drivers/adv_pci1710.c1100
-rw-r--r--drivers/staging/comedi/drivers/adv_pci1723.c233
-rw-r--r--drivers/staging/comedi/drivers/adv_pci1724.c220
-rw-r--r--drivers/staging/comedi/drivers/adv_pci_dio.c1113
-rw-r--r--drivers/staging/comedi/drivers/aio_aio12_8.c249
-rw-r--r--drivers/staging/comedi/drivers/aio_iiro_16.c244
-rw-r--r--drivers/staging/comedi/drivers/amcc_s5933.h176
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200.c273
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200.h51
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200_common.c874
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200_pci.c423
-rw-r--r--drivers/staging/comedi/drivers/amplc_pc236.c85
-rw-r--r--drivers/staging/comedi/drivers/amplc_pc236.h42
-rw-r--r--drivers/staging/comedi/drivers/amplc_pc236_common.c200
-rw-r--r--drivers/staging/comedi/drivers/amplc_pc263.c111
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci224.c1136
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci230.c2568
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci236.c153
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci263.c114
-rw-r--r--drivers/staging/comedi/drivers/c6xdigio.c307
-rw-r--r--drivers/staging/comedi/drivers/cb_das16_cs.c355
-rw-r--r--drivers/staging/comedi/drivers/cb_pcidas.c1582
-rw-r--r--drivers/staging/comedi/drivers/cb_pcidas64.c4079
-rw-r--r--drivers/staging/comedi/drivers/cb_pcidda.c428
-rw-r--r--drivers/staging/comedi/drivers/cb_pcimdas.c482
-rw-r--r--drivers/staging/comedi/drivers/cb_pcimdda.c206
-rw-r--r--drivers/staging/comedi/drivers/comedi_8254.c664
-rw-r--r--drivers/staging/comedi/drivers/comedi_8254.h133
-rw-r--r--drivers/staging/comedi/drivers/comedi_bond.c355
-rw-r--r--drivers/staging/comedi/drivers/comedi_isadma.c264
-rw-r--r--drivers/staging/comedi/drivers/comedi_isadma.h116
-rw-r--r--drivers/staging/comedi/drivers/comedi_parport.c314
-rw-r--r--drivers/staging/comedi/drivers/comedi_test.c456
-rw-r--r--drivers/staging/comedi/drivers/contec_pci_dio.c125
-rw-r--r--drivers/staging/comedi/drivers/dac02.c150
-rw-r--r--drivers/staging/comedi/drivers/daqboard2000.c764
-rw-r--r--drivers/staging/comedi/drivers/das08.c489
-rw-r--r--drivers/staging/comedi/drivers/das08.h51
-rw-r--r--drivers/staging/comedi/drivers/das08_cs.c114
-rw-r--r--drivers/staging/comedi/drivers/das08_isa.c199
-rw-r--r--drivers/staging/comedi/drivers/das08_pci.c105
-rw-r--r--drivers/staging/comedi/drivers/das16.c1207
-rw-r--r--drivers/staging/comedi/drivers/das16m1.c644
-rw-r--r--drivers/staging/comedi/drivers/das1800.c1455
-rw-r--r--drivers/staging/comedi/drivers/das6402.c676
-rw-r--r--drivers/staging/comedi/drivers/das800.c748
-rw-r--r--drivers/staging/comedi/drivers/dmm32at.c627
-rw-r--r--drivers/staging/comedi/drivers/dt2801.c639
-rw-r--r--drivers/staging/comedi/drivers/dt2811.c474
-rw-r--r--drivers/staging/comedi/drivers/dt2814.c297
-rw-r--r--drivers/staging/comedi/drivers/dt2815.c223
-rw-r--r--drivers/staging/comedi/drivers/dt2817.c149
-rw-r--r--drivers/staging/comedi/drivers/dt282x.c1211
-rw-r--r--drivers/staging/comedi/drivers/dt3000.c769
-rw-r--r--drivers/staging/comedi/drivers/dt9812.c885
-rw-r--r--drivers/staging/comedi/drivers/dyna_pci10xx.c282
-rw-r--r--drivers/staging/comedi/drivers/fl512.c156
-rw-r--r--drivers/staging/comedi/drivers/gsc_hpdi.c752
-rw-r--r--drivers/staging/comedi/drivers/icp_multi.c568
-rw-r--r--drivers/staging/comedi/drivers/ii_pci20kc.c526
-rw-r--r--drivers/staging/comedi/drivers/jr3_pci.c828
-rw-r--r--drivers/staging/comedi/drivers/jr3_pci.h682
-rw-r--r--drivers/staging/comedi/drivers/ke_counter.c240
-rw-r--r--drivers/staging/comedi/drivers/me4000.c1443
-rw-r--r--drivers/staging/comedi/drivers/me_daq.c583
-rw-r--r--drivers/staging/comedi/drivers/mf6x4.c330
-rw-r--r--drivers/staging/comedi/drivers/mite.c626
-rw-r--r--drivers/staging/comedi/drivers/mite.h347
-rw-r--r--drivers/staging/comedi/drivers/mpc624.c357
-rw-r--r--drivers/staging/comedi/drivers/multiq3.c289
-rw-r--r--drivers/staging/comedi/drivers/ni_6527.c500
-rw-r--r--drivers/staging/comedi/drivers/ni_65xx.c831
-rw-r--r--drivers/staging/comedi/drivers/ni_660x.c1222
-rw-r--r--drivers/staging/comedi/drivers/ni_670x.c296
-rw-r--r--drivers/staging/comedi/drivers/ni_at_a2150.c796
-rw-r--r--drivers/staging/comedi/drivers/ni_at_ao.c383
-rw-r--r--drivers/staging/comedi/drivers/ni_atmio.c378
-rw-r--r--drivers/staging/comedi/drivers/ni_atmio16d.c760
-rw-r--r--drivers/staging/comedi/drivers/ni_daq_700.c289
-rw-r--r--drivers/staging/comedi/drivers/ni_daq_dio24.c95
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc.c125
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc.h69
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_common.c1355
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_cs.c128
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_isadma.c190
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_isadma.h42
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_pci.c141
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_regs.h75
-rw-r--r--drivers/staging/comedi/drivers/ni_mio_common.c5703
-rw-r--r--drivers/staging/comedi/drivers/ni_mio_cs.c228
-rw-r--r--drivers/staging/comedi/drivers/ni_pcidio.c1024
-rw-r--r--drivers/staging/comedi/drivers/ni_pcimio.c1308
-rw-r--r--drivers/staging/comedi/drivers/ni_stc.h1491
-rw-r--r--drivers/staging/comedi/drivers/ni_tio.c1436
-rw-r--r--drivers/staging/comedi/drivers/ni_tio.h154
-rw-r--r--drivers/staging/comedi/drivers/ni_tio_internal.h245
-rw-r--r--drivers/staging/comedi/drivers/ni_tiocmd.c484
-rw-r--r--drivers/staging/comedi/drivers/ni_usb6501.c620
-rw-r--r--drivers/staging/comedi/drivers/pcl711.c521
-rw-r--r--drivers/staging/comedi/drivers/pcl724.c152
-rw-r--r--drivers/staging/comedi/drivers/pcl726.c432
-rw-r--r--drivers/staging/comedi/drivers/pcl730.c349
-rw-r--r--drivers/staging/comedi/drivers/pcl812.c1317
-rw-r--r--drivers/staging/comedi/drivers/pcl816.c712
-rw-r--r--drivers/staging/comedi/drivers/pcl818.c1146
-rw-r--r--drivers/staging/comedi/drivers/pcm3724.c220
-rw-r--r--drivers/staging/comedi/drivers/pcmad.c158
-rw-r--r--drivers/staging/comedi/drivers/pcmda12.c174
-rw-r--r--drivers/staging/comedi/drivers/pcmmio.c786
-rw-r--r--drivers/staging/comedi/drivers/pcmuio.c633
-rw-r--r--drivers/staging/comedi/drivers/plx9052.h79
-rw-r--r--drivers/staging/comedi/drivers/plx9080.h422
-rw-r--r--drivers/staging/comedi/drivers/quatech_daqp_cs.c815
-rw-r--r--drivers/staging/comedi/drivers/rtd520.c1344
-rw-r--r--drivers/staging/comedi/drivers/rti800.c362
-rw-r--r--drivers/staging/comedi/drivers/rti802.c129
-rw-r--r--drivers/staging/comedi/drivers/s526.c614
-rw-r--r--drivers/staging/comedi/drivers/s626.c2925
-rw-r--r--drivers/staging/comedi/drivers/s626.h760
-rw-r--r--drivers/staging/comedi/drivers/serial2002.c798
-rw-r--r--drivers/staging/comedi/drivers/ssv_dnp.c186
-rw-r--r--drivers/staging/comedi/drivers/unioxx5.c506
-rw-r--r--drivers/staging/comedi/drivers/usbdux.c1751
-rw-r--r--drivers/staging/comedi/drivers/usbduxfast.c1107
-rw-r--r--drivers/staging/comedi/drivers/usbduxsigma.c1659
-rw-r--r--drivers/staging/comedi/drivers/vmk80xx.c885
-rw-r--r--drivers/staging/comedi/drivers/z8536.h202
-rw-r--r--drivers/staging/comedi/kcomedilib/Makefile5
-rw-r--r--drivers/staging/comedi/kcomedilib/kcomedilib_main.c252
-rw-r--r--drivers/staging/comedi/proc.c97
-rw-r--r--drivers/staging/comedi/range.c132
174 files changed, 99896 insertions, 0 deletions
diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig
new file mode 100644
index 000000000..61c6351f5
--- /dev/null
+++ b/drivers/staging/comedi/Kconfig
@@ -0,0 +1,1306 @@
+config COMEDI
+ tristate "Data acquisition support (comedi)"
+ depends on m
+ ---help---
+ Enable support a wide range of data acquisition devices
+ for Linux.
+
+if COMEDI
+
+config COMEDI_DEBUG
+ bool "Comedi debugging"
+ ---help---
+ This is an option for use by developers; most people should
+ say N here. This enables comedi core and driver debugging.
+
+config COMEDI_DEFAULT_BUF_SIZE_KB
+ int "Comedi default initial asynchronous buffer size in KiB"
+ default "2048"
+ ---help---
+ This is the default asynchronous buffer size which is used for
+ commands running in the background in kernel space. This
+ defaults to 2048 KiB of memory so that a 16 channel card
+ running at 10 kHz has of 2-4 seconds of buffer.
+
+config COMEDI_DEFAULT_BUF_MAXSIZE_KB
+ int "Comedi default maximum asynchronous buffer size in KiB"
+ default "20480"
+ ---help---
+ This is the default maximum asynchronous buffer size which can
+ be requested by a userspace program without root privileges.
+ This is set to 20480 KiB so that a fast I/O card with 16
+ channels running at 100 kHz has 2-4 seconds of buffer.
+
+menuconfig COMEDI_MISC_DRIVERS
+ bool "Comedi misc drivers"
+ ---help---
+ Enable comedi misc drivers to be built
+
+ Note that the answer to this question won't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about misc non-hardware comedi drivers.
+
+if COMEDI_MISC_DRIVERS
+
+config COMEDI_BOND
+ tristate "Comedi device bonding support"
+ select COMEDI_KCOMEDILIB
+ ---help---
+ Enable support for a driver to 'bond' (merge) multiple subdevices
+ from multiple devices together as one.
+
+ Currently, it only handles digital I/O subdevices.
+
+ To compile this driver as a module, choose M here: the module will be
+ called comedi_bond.
+
+config COMEDI_TEST
+ tristate "Fake waveform generator support"
+ ---help---
+ Enable support for the fake waveform generator.
+ This driver is mainly for testing purposes, but can also be used to
+ generate sample waveforms on systems that don't have data acquisition
+ hardware.
+
+ To compile this driver as a module, choose M here: the module will be
+ called comedi_test.
+
+config COMEDI_PARPORT
+ tristate "Parallel port support"
+ ---help---
+ Enable support for the standard parallel port.
+ A cheap and easy way to get a few more digital I/O lines. Steal
+ additional parallel ports from old computers or your neighbors'
+ computers.
+
+ To compile this driver as a module, choose M here: the module will be
+ called comedi_parport.
+
+config COMEDI_SERIAL2002
+ tristate "Driver for serial connected hardware"
+ ---help---
+ Enable support for serial connected hardware
+
+ To compile this driver as a module, choose M here: the module will be
+ called serial2002.
+
+config COMEDI_SSV_DNP
+ tristate "SSV Embedded Systems DIL/Net-PC support"
+ depends on X86_32 || COMPILE_TEST
+ ---help---
+ Enable support for SSV Embedded Systems DIL/Net-PC
+
+ To compile this driver as a module, choose M here: the module will be
+ called ssv_dnp.
+
+endif # COMEDI_MISC_DRIVERS
+
+menuconfig COMEDI_ISA_DRIVERS
+ bool "Comedi ISA and PC/104 drivers"
+ ---help---
+ Enable comedi ISA and PC/104 drivers to be built
+
+ Note that the answer to this question won't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about ISA and PC/104 comedi drivers.
+
+if COMEDI_ISA_DRIVERS
+
+config COMEDI_PCL711
+ tristate "Advantech PCL-711/711b and ADlink ACL-8112 ISA card support"
+ select COMEDI_8254
+ ---help---
+ Enable support for Advantech PCL-711 and 711b, ADlink ACL-8112
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl711.
+
+config COMEDI_PCL724
+ tristate "Advantech PCL-722/724/731 and ADlink ACL-7122/7124/PET-48DIO"
+ select COMEDI_8255
+ ---help---
+ Enable support for ISA and PC/104 based 8255 digital i/o boards. This
+ driver provides a legacy comedi driver wrapper for the generic 8255
+ support driver.
+
+ Supported boards include:
+ Advantech PCL-724 24 channels
+ Advantech PCL-722 144 (or 96) channels
+ Advantech PCL-731 48 channels
+ ADlink ACL-7122 144 (or 96) channels
+ ADlink ACL-7124 24 channels
+ ADlink PET-48DIO 48 channels
+ WinSystems PCM-IO48 48 channels (PC/104)
+ Diamond Systems ONYX-MM-DIO 48 channels (PC/104)
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl724.
+
+config COMEDI_PCL726
+ tristate "Advantech PCL-726 and compatible ISA card support"
+ ---help---
+ Enable support for Advantech PCL-726 and compatible ISA cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl726.
+
+config COMEDI_PCL730
+ tristate "Simple Digital I/O board support (8-bit ports)"
+ ---help---
+ Enable support for various simple ISA or PC/104 Digital I/O boards.
+ These boards all use 8-bit I/O ports.
+
+ Advantech PCL-730 iso - 16 in/16 out ttl - 16 in/16 out
+ ICP ISO-730 iso - 16 in/16 out ttl - 16 in/16 out
+ ADlink ACL-7130 iso - 16 in/16 out ttl - 16 in/16 out
+ Advantech PCM-3730 iso - 8 in/8 out ttl - 16 in/16 out
+ Advantech PCL-725 iso - 8 in/8 out
+ ICP P8R8-DIO iso - 8 in/8 out
+ ADlink ACL-7225b iso - 16 in/16 out
+ ICP P16R16-DIO iso - 16 in/16 out
+ Advantech PCL-733 iso - 32 in
+ Advantech PCL-734 iso - 32 out
+ Diamond Systems OPMM-1616-XT iso - 16 in/16 out
+ Diamond Systems PEARL-MM-P iso - 16 out
+ Diamond Systems IR104-PBF iso - 20 in/20 out
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl730.
+
+config COMEDI_PCL812
+ tristate "Advantech PCL-812/813 and ADlink ACL-8112/8113/8113/8216"
+ select COMEDI_ISADMA if ISA_DMA_API
+ select COMEDI_8254
+ ---help---
+ Enable support for Advantech PCL-812/PG, PCL-813/B, ADLink
+ ACL-8112DG/HG/PG, ACL-8113, ACL-8216, ICP DAS A-821PGH/PGL/PGL-NDA,
+ A-822PGH/PGL, A-823PGH/PGL, A-826PG and ICP DAS ISO-813 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl812.
+
+config COMEDI_PCL816
+ tristate "Advantech PCL-814 and PCL-816 ISA card support"
+ select COMEDI_ISADMA if ISA_DMA_API
+ select COMEDI_8254
+ ---help---
+ Enable support for Advantech PCL-814 and PCL-816 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl816.
+
+config COMEDI_PCL818
+ tristate "Advantech PCL-718 and PCL-818 ISA card support"
+ select COMEDI_ISADMA if ISA_DMA_API
+ select COMEDI_8254
+ ---help---
+ Enable support for Advantech PCL-818 ISA cards
+ PCL-818L, PCL-818H, PCL-818HD, PCL-818HG, PCL-818 and PCL-718
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl818.
+
+config COMEDI_PCM3724
+ tristate "Advantech PCM-3724 PC/104 card support"
+ select COMEDI_8255
+ ---help---
+ Enable support for Advantech PCM-3724 PC/104 cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcm3724.
+
+config COMEDI_AMPLC_DIO200_ISA
+ tristate "Amplicon PC212E/PC214E/PC215E/PC218E/PC272E"
+ select COMEDI_AMPLC_DIO200
+ ---help---
+ Enable support for Amplicon PC212E, PC214E, PC215E, PC218E and
+ PC272E ISA DIO boards
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_dio200.
+
+config COMEDI_AMPLC_PC236_ISA
+ tristate "Amplicon PC36AT DIO board support"
+ select COMEDI_AMPLC_PC236
+ ---help---
+ Enable support for Amplicon PC36AT ISA DIO board.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pc236.
+
+config COMEDI_AMPLC_PC263_ISA
+ tristate "Amplicon PC263 relay board support"
+ ---help---
+ Enable support for Amplicon PC263 ISA relay board. This board has
+ 16 reed relay output channels.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pc263.
+
+config COMEDI_RTI800
+ tristate "Analog Devices RTI-800/815 ISA card support"
+ ---help---
+ Enable support for Analog Devices RTI-800/815 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called rti800.
+
+config COMEDI_RTI802
+ tristate "Analog Devices RTI-802 ISA card support"
+ ---help---
+ Enable support for Analog Devices RTI-802 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called rti802.
+
+config COMEDI_DAC02
+ tristate "Keithley Metrabyte DAC02 compatible ISA card support"
+ ---help---
+ Enable support for Keithley Metrabyte DAC02 compatible ISA cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called dac02.
+
+config COMEDI_DAS16M1
+ tristate "MeasurementComputing CIO-DAS16/M1DAS-16 ISA card support"
+ select COMEDI_8254
+ select COMEDI_8255
+ ---help---
+ Enable support for Measurement Computing CIO-DAS16/M1 ISA cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called das16m1.
+
+config COMEDI_DAS08_ISA
+ tristate "DAS-08 compatible ISA and PC/104 card support"
+ select COMEDI_DAS08
+ ---help---
+ Enable support for Keithley Metrabyte/ComputerBoards DAS08
+ and compatible ISA and PC/104 cards:
+ Keithley Metrabyte/ComputerBoards DAS08, DAS08-PGM, DAS08-PGH,
+ DAS08-PGL, DAS08-AOH, DAS08-AOL, DAS08-AOM, DAS08/JR-AO,
+ DAS08/JR-16-AO, PC104-DAS08, DAS08/JR/16.
+
+ To compile this driver as a module, choose M here: the module will be
+ called das08_isa.
+
+config COMEDI_DAS16
+ tristate "DAS-16 compatible ISA and PC/104 card support"
+ select COMEDI_ISADMA if ISA_DMA_API
+ select COMEDI_8254
+ select COMEDI_8255
+ ---help---
+ Enable support for Keithley Metrabyte/ComputerBoards DAS16
+ and compatible ISA and PC/104 cards:
+ Keithley Metrabyte DAS-16, DAS-16G, DAS-16F, DAS-1201, DAS-1202,
+ DAS-1401, DAS-1402, DAS-1601, DAS-1602 and
+ ComputerBoards/MeasurementComputing PC104-DAS16/JR/,
+ PC104-DAS16JR/16, CIO-DAS16JR/16, CIO-DAS16/JR, CIO-DAS1401/12,
+ CIO-DAS1402/12, CIO-DAS1402/16, CIO-DAS1601/12, CIO-DAS1602/12,
+ CIO-DAS1602/16, CIO-DAS16/330
+
+ To compile this driver as a module, choose M here: the module will be
+ called das16.
+
+config COMEDI_DAS800
+ tristate "DAS800 and compatible ISA card support"
+ select COMEDI_8254
+ ---help---
+ Enable support for Keithley Metrabyte DAS800 and compatible ISA cards
+ Keithley Metrabyte DAS-800, DAS-801, DAS-802
+ Measurement Computing CIO-DAS800, CIO-DAS801, CIO-DAS802 and
+ CIO-DAS802/16
+
+ To compile this driver as a module, choose M here: the module will be
+ called das800.
+
+config COMEDI_DAS1800
+ tristate "DAS1800 and compatible ISA card support"
+ select COMEDI_ISADMA if ISA_DMA_API
+ select COMEDI_8254
+ ---help---
+ Enable support for DAS1800 and compatible ISA cards
+ Keithley Metrabyte DAS-1701ST, DAS-1701ST-DA, DAS-1701/AO,
+ DAS-1702ST, DAS-1702ST-DA, DAS-1702HR, DAS-1702HR-DA, DAS-1702/AO,
+ DAS-1801ST, DAS-1801ST-DA, DAS-1801HC, DAS-1801AO, DAS-1802ST,
+ DAS-1802ST-DA, DAS-1802HR, DAS-1802HR-DA, DAS-1802HC and
+ DAS-1802AO
+
+ To compile this driver as a module, choose M here: the module will be
+ called das1800.
+
+config COMEDI_DAS6402
+ tristate "DAS6402 and compatible ISA card support"
+ select COMEDI_8254
+ ---help---
+ Enable support for DAS6402 and compatible ISA cards
+ Computerboards, Keithley Metrabyte DAS6402 and compatibles
+
+ To compile this driver as a module, choose M here: the module will be
+ called das6402.
+
+config COMEDI_DT2801
+ tristate "Data Translation DT2801 ISA card support"
+ ---help---
+ Enable support for Data Translation DT2801 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt2801.
+
+config COMEDI_DT2811
+ tristate "Data Translation DT2811 ISA card support"
+ ---help---
+ Enable support for Data Translation DT2811 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt2811.
+
+config COMEDI_DT2814
+ tristate "Data Translation DT2814 ISA card support"
+ ---help---
+ Enable support for Data Translation DT2814 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt2814.
+
+config COMEDI_DT2815
+ tristate "Data Translation DT2815 ISA card support"
+ ---help---
+ Enable support for Data Translation DT2815 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt2815.
+
+config COMEDI_DT2817
+ tristate "Data Translation DT2817 ISA card support"
+ ---help---
+ Enable support for Data Translation DT2817 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt2817.
+
+config COMEDI_DT282X
+ tristate "Data Translation DT2821 series and DT-EZ ISA card support"
+ select COMEDI_ISADMA if ISA_DMA_API
+ ---help---
+ Enable support for Data Translation DT2821 series including DT-EZ
+ DT2821, DT2821-F-16SE, DT2821-F-8DI, DT2821-G-16SE, DT2821-G-8DI,
+ DT2823 (dt2823), DT2824-PGH, DT2824-PGL, DT2825, DT2827, DT2828,
+ DT21-EZ, DT23-EZ, DT24-EZ and DT24-EZ-PGL
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt282x.
+
+config COMEDI_DMM32AT
+ tristate "Diamond Systems MM-32-AT PC/104 board support"
+ select COMEDI_8255
+ ---help---
+ Enable support for Diamond Systems MM-32-AT PC/104 boards
+
+ To compile this driver as a module, choose M here: the module will be
+ called dmm32at.
+
+config COMEDI_UNIOXX5
+ tristate "Fastwel UNIOxx-5 analog and digital io board support"
+ ---help---
+ Enable support for Fastwel UNIOxx-5 (analog and digital i/o) boards
+
+ To compile this driver as a module, choose M here: the module will be
+ called unioxx5.
+
+config COMEDI_FL512
+ tristate "FL512 ISA card support"
+ ---help---
+ Enable support for FL512 ISA card
+
+ To compile this driver as a module, choose M here: the module will be
+ called fl512.
+
+config COMEDI_AIO_AIO12_8
+ tristate "I/O Products PC/104 AIO12-8 Analog I/O Board support"
+ select COMEDI_8255
+ ---help---
+ Enable support for I/O Products PC/104 AIO12-8 Analog I/O Board
+
+ To compile this driver as a module, choose M here: the module will be
+ called aio_aio12_8.
+
+config COMEDI_AIO_IIRO_16
+ tristate "I/O Products PC/104 IIRO16 Board support"
+ ---help---
+ Enable support for I/O Products PC/104 IIRO16 Relay And Isolated
+ Input Board
+
+ To compile this driver as a module, choose M here: the module will be
+ called aio_iiro_16.
+
+config COMEDI_II_PCI20KC
+ tristate "Intelligent Instruments PCI-20001C carrier support"
+ depends on HAS_IOMEM
+ ---help---
+ Enable support for Intelligent Instruments PCI-20001C carrier
+ PCI-20001, PCI-20006 and PCI-20341
+
+ To compile this driver as a module, choose M here: the module will be
+ called ii_pci20kc.
+
+config COMEDI_C6XDIGIO
+ tristate "Mechatronic Systems Inc. C6x_DIGIO DSP daughter card support"
+ ---help---
+ Enable support for Mechatronic Systems Inc. C6x_DIGIO DSP daughter
+ card
+
+ To compile this driver as a module, choose M here: the module will be
+ called c6xdigio.
+
+config COMEDI_MPC624
+ tristate "Micro/sys MPC-624 PC/104 board support"
+ ---help---
+ Enable support for Micro/sys MPC-624 PC/104 board
+
+ To compile this driver as a module, choose M here: the module will be
+ called mpc624.
+
+config COMEDI_ADQ12B
+ tristate "MicroAxial ADQ12-B data acquisition and control card support"
+ ---help---
+ Enable MicroAxial ADQ12-B daq and control card support.
+
+ To compile this driver as a module, choose M here: the module will be
+ called adq12b.
+
+config COMEDI_NI_AT_A2150
+ tristate "NI AT-A2150 ISA card support"
+ select COMEDI_ISADMA if ISA_DMA_API
+ select COMEDI_8254
+ ---help---
+ Enable support for National Instruments AT-A2150 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_at_a2150.
+
+config COMEDI_NI_AT_AO
+ tristate "NI AT-AO-6/10 EISA card support"
+ select COMEDI_8254
+ ---help---
+ Enable support for National Instruments AT-AO-6/10 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_at_ao.
+
+config COMEDI_NI_ATMIO
+ tristate "NI AT-MIO E series ISA-PNP card support"
+ select COMEDI_8255
+ select COMEDI_NI_TIO
+ ---help---
+ Enable support for National Instruments AT-MIO E series cards
+ National Instruments AT-MIO-16E-1 (ni_atmio),
+ AT-MIO-16E-2, AT-MIO-16E-10, AT-MIO-16DE-10, AT-MIO-64E-3,
+ AT-MIO-16XE-50, AT-MIO-16XE-10, AT-AI-16XE-10
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_atmio.
+
+config COMEDI_NI_ATMIO16D
+ tristate "NI AT-MIO-16/AT-MIO-16D series ISA card support"
+ select COMEDI_8255
+ ---help---
+ Enable support for National Instruments AT-MIO-16/AT-MIO-16D cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_atmio16d.
+
+config COMEDI_NI_LABPC_ISA
+ tristate "NI Lab-PC and compatibles ISA support"
+ select COMEDI_NI_LABPC
+ select COMEDI_NI_LABPC_ISADMA if ISA_DMA_API
+ ---help---
+ Enable support for National Instruments Lab-PC and compatibles
+ Lab-PC-1200, Lab-PC-1200AI, Lab-PC+.
+ Kernel-level ISA plug-and-play support for the lab-pc-1200 boards has
+ not yet been added to the driver.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_labpc.
+
+config COMEDI_PCMAD
+ tristate "Winsystems PCM-A/D12 and PCM-A/D16 PC/104 board support"
+ ---help---
+ Enable support for Winsystems PCM-A/D12 and PCM-A/D16 PC/104 boards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcmad.
+
+config COMEDI_PCMDA12
+ tristate "Winsystems PCM-D/A-12 8-channel AO PC/104 board support"
+ ---help---
+ Enable support for Winsystems PCM-D/A-12 8-channel AO PC/104 boards.
+ Note that the board is not ISA-PNP capable and thus needs the I/O
+ port comedi_config parameter.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcmda12.
+
+config COMEDI_PCMMIO
+ tristate "Winsystems PCM-MIO PC/104 board support"
+ ---help---
+ Enable support for Winsystems PCM-MIO multifunction PC/104 boards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcmmio.
+
+config COMEDI_PCMUIO
+ tristate "Winsystems PCM-UIO48A and PCM-UIO96A PC/104 board support"
+ ---help---
+ Enable support for PCM-UIO48A and PCM-UIO96A PC/104 boards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcmuio.
+
+config COMEDI_MULTIQ3
+ tristate "Quanser Consulting MultiQ-3 ISA card support"
+ ---help---
+ Enable support for Quanser Consulting MultiQ-3 ISA cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called multiq3.
+
+config COMEDI_S526
+ tristate "Sensoray s526 support"
+ ---help---
+ Enable support for Sensoray s526
+
+ To compile this driver as a module, choose M here: the module will be
+ called s526.
+
+endif # COMEDI_ISA_DRIVERS
+
+menuconfig COMEDI_PCI_DRIVERS
+ tristate "Comedi PCI drivers"
+ depends on PCI
+ ---help---
+ Enable support for comedi PCI drivers.
+
+ To compile this support as a module, choose M here: the module will
+ be called comedi_pci.
+
+if COMEDI_PCI_DRIVERS
+
+config COMEDI_8255_PCI
+ tristate "Generic PCI based 8255 digital i/o board support"
+ select COMEDI_8255
+ ---help---
+ Enable support for PCI based 8255 digital i/o boards. This driver
+ provides a PCI wrapper around the generic 8255 driver.
+
+ Supported boards:
+ ADlink - PCI-7224, PCI-7248, and PCI-7296
+ Measurement Computing - PCI-DIO24, PCI-DIO24H, PCI-DIO48H and
+ PCI-DIO96H
+ National Instruments - PCI-DIO-96, PCI-DIO-96B, PXI-6508, PCI-6503,
+ PCI-6503B, PCI-6503X, and PXI-6503
+
+ To compile this driver as a module, choose M here: the module will
+ be called 8255_pci.
+
+config COMEDI_ADDI_WATCHDOG
+ tristate
+ ---help---
+ Provides support for the watchdog subdevice found on many ADDI-DATA
+ boards. This module will be automatically selected when needed. The
+ module will be called addi_watchdog.
+
+config COMEDI_ADDI_APCI_1032
+ tristate "ADDI-DATA APCI_1032 support"
+ ---help---
+ Enable support for ADDI-DATA APCI_1032 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_1032.
+
+config COMEDI_ADDI_APCI_1500
+ tristate "ADDI-DATA APCI_1500 support"
+ ---help---
+ Enable support for ADDI-DATA APCI_1500 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_1500.
+
+config COMEDI_ADDI_APCI_1516
+ tristate "ADDI-DATA APCI-1016/1516/2016 support"
+ select COMEDI_ADDI_WATCHDOG
+ ---help---
+ Enable support for ADDI-DATA APCI-1016, APCI-1516 and APCI-2016 boards.
+ These are 16 channel, optically isolated, digital I/O boards. The 1516
+ and 2016 boards also have a watchdog for resetting the outputs to "0".
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_1516.
+
+config COMEDI_ADDI_APCI_1564
+ tristate "ADDI-DATA APCI_1564 support"
+ select COMEDI_ADDI_WATCHDOG
+ ---help---
+ Enable support for ADDI-DATA APCI_1564 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_1564.
+
+config COMEDI_ADDI_APCI_16XX
+ tristate "ADDI-DATA APCI_16xx support"
+ ---help---
+ Enable support for ADDI-DATA APCI_16xx cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_16xx.
+
+config COMEDI_ADDI_APCI_2032
+ tristate "ADDI-DATA APCI_2032 support"
+ select COMEDI_ADDI_WATCHDOG
+ ---help---
+ Enable support for ADDI-DATA APCI_2032 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_2032.
+
+config COMEDI_ADDI_APCI_2200
+ tristate "ADDI-DATA APCI_2200 support"
+ select COMEDI_ADDI_WATCHDOG
+ ---help---
+ Enable support for ADDI-DATA APCI_2200 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_2200.
+
+config COMEDI_ADDI_APCI_3120
+ tristate "ADDI-DATA APCI_3120/3001 support"
+ depends on HAS_DMA
+ ---help---
+ Enable support for ADDI-DATA APCI_3120/3001 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_3120.
+
+config COMEDI_ADDI_APCI_3501
+ tristate "ADDI-DATA APCI_3501 support"
+ ---help---
+ Enable support for ADDI-DATA APCI_3501 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_3501.
+
+config COMEDI_ADDI_APCI_3XXX
+ tristate "ADDI-DATA APCI_3xxx support"
+ ---help---
+ Enable support for ADDI-DATA APCI_3xxx cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called addi_apci_3xxx.
+
+config COMEDI_ADL_PCI6208
+ tristate "ADLink PCI-6208A support"
+ ---help---
+ Enable support for ADLink PCI-6208A cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called adl_pci6208.
+
+config COMEDI_ADL_PCI7X3X
+ tristate "ADLink PCI-723X/743X isolated digital i/o board support"
+ ---help---
+ Enable support for ADlink PCI-723X/743X isolated digital i/o boards.
+ Supported boards include the 32-channel PCI-7230 (16 in/16 out),
+ PCI-7233 (32 in), and PCI-7234 (32 out) as well as the 64-channel
+ PCI-7432 (32 in/32 out), PCI-7433 (64 in), and PCI-7434 (64 out).
+
+ To compile this driver as a module, choose M here: the module will be
+ called adl_pci7x3x.
+
+config COMEDI_ADL_PCI8164
+ tristate "ADLink PCI-8164 4 Axes Motion Control board support"
+ ---help---
+ Enable support for ADlink PCI-8164 4 Axes Motion Control board
+
+ To compile this driver as a module, choose M here: the module will be
+ called adl_pci8164.
+
+config COMEDI_ADL_PCI9111
+ tristate "ADLink PCI-9111HR support"
+ select COMEDI_8254
+ ---help---
+ Enable support for ADlink PCI9111 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called adl_pci9111.
+
+config COMEDI_ADL_PCI9118
+ tristate "ADLink PCI-9118DG, PCI-9118HG, PCI-9118HR support"
+ depends on HAS_DMA
+ select COMEDI_8254
+ ---help---
+ Enable support for ADlink PCI-9118DG, PCI-9118HG, PCI-9118HR cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called adl_pci9118.
+
+config COMEDI_ADV_PCI1710
+ tristate "Advantech PCI-171x, PCI-1720 and PCI-1731 support"
+ select COMEDI_8254
+ ---help---
+ Enable support for Advantech PCI-1710, PCI-1710HG, PCI-1711,
+ PCI-1713, PCI-1720 and PCI-1731
+
+ To compile this driver as a module, choose M here: the module will be
+ called adv_pci1710.
+
+config COMEDI_ADV_PCI1723
+ tristate "Advantech PCI-1723 support"
+ ---help---
+ Enable support for Advantech PCI-1723 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called adv_pci1723.
+
+config COMEDI_ADV_PCI1724
+ tristate "Advantech PCI-1724U support"
+ ---help---
+ Enable support for Advantech PCI-1724U cards. These are 32-channel
+ analog output cards with voltage and current loop output ranges and
+ 14-bit resolution.
+
+ To compile this driver as a module, choose M here: the module will be
+ called adv_pci1724.
+
+config COMEDI_ADV_PCI_DIO
+ tristate "Advantech PCI DIO card support"
+ select COMEDI_8254
+ select COMEDI_8255
+ ---help---
+ Enable support for Advantech PCI DIO cards
+ PCI-1730, PCI-1733, PCI-1734, PCI-1735U, PCI-1736UP, PCI-1739U,
+ PCI-1750, PCI-1751, PCI-1752, PCI-1753/E, PCI-1754, PCI-1756,
+ PCI-1760 and PCI-1762
+
+ To compile this driver as a module, choose M here: the module will be
+ called adv_pci_dio.
+
+config COMEDI_AMPLC_DIO200_PCI
+ tristate "Amplicon PCI215/PCI272/PCIe215/PCIe236/PCIe296 DIO support"
+ select COMEDI_AMPLC_DIO200
+ ---help---
+ Enable support for Amplicon PCI215, PCI272, PCIe215, PCIe236
+ and PCIe296 DIO boards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_dio200_pci.
+
+config COMEDI_AMPLC_PC236_PCI
+ tristate "Amplicon PCI236 DIO board support"
+ select COMEDI_AMPLC_PC236
+ ---help---
+ Enable support for Amplicon PCI236 DIO board.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pci236.
+
+config COMEDI_AMPLC_PC263_PCI
+ tristate "Amplicon PCI263 relay board support"
+ ---help---
+ Enable support for Amplicon PCI263 relay board. This is a PCI board
+ with 16 reed relay output channels.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pci263.
+
+config COMEDI_AMPLC_PCI224
+ tristate "Amplicon PCI224 and PCI234 support"
+ select COMEDI_8254
+ ---help---
+ Enable support for Amplicon PCI224 and PCI234 AO boards
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pci224.
+
+config COMEDI_AMPLC_PCI230
+ tristate "Amplicon PCI230 and PCI260 support"
+ select COMEDI_8254
+ select COMEDI_8255
+ ---help---
+ Enable support for Amplicon PCI230 and PCI260 Multifunction I/O
+ boards
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pci230.
+
+config COMEDI_CONTEC_PCI_DIO
+ tristate "Contec PIO1616L digital I/O board support"
+ ---help---
+ Enable support for the Contec PIO1616L digital I/O board
+
+ To compile this driver as a module, choose M here: the module will be
+ called contec_pci_dio.
+
+config COMEDI_DAS08_PCI
+ tristate "DAS-08 PCI support"
+ select COMEDI_DAS08
+ ---help---
+ Enable support for PCI DAS-08 cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called das08_pci.
+
+config COMEDI_DT3000
+ tristate "Data Translation DT3000 series support"
+ ---help---
+ Enable support for Data Translation DT3000 series
+ DT3001, DT3001-PGL, DT3002, DT3003, DT3003-PGL, DT3004, DT3005 and
+ DT3004-200
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt3000.
+
+config COMEDI_DYNA_PCI10XX
+ tristate "Dynalog PCI DAQ series support"
+ ---help---
+ Enable support for Dynalog PCI DAQ series
+ PCI-1050
+
+ To compile this driver as a module, choose M here: the module will be
+ called dyna_pci10xx.
+
+config COMEDI_GSC_HPDI
+ tristate "General Standards PCI-HPDI32 / PMC-HPDI32 support"
+ ---help---
+ Enable support for General Standards Corporation high speed parallel
+ digital interface rs485 boards PCI-HPDI32 and PMC-HPDI32.
+ Only receive mode works, transmit not supported.
+
+ To compile this driver as a module, choose M here: the module will be
+ called gsc_hpdi.
+
+config COMEDI_MF6X4
+ tristate "Humusoft MF634 and MF624 DAQ Card support"
+ ---help---
+ This driver supports both Humusoft MF634 and MF624 Data acquisition
+ cards. The legacy Humusoft MF614 card is not supported.
+
+config COMEDI_ICP_MULTI
+ tristate "Inova ICP_MULTI support"
+ ---help---
+ Enable support for Inova ICP_MULTI card
+
+ To compile this driver as a module, choose M here: the module will be
+ called icp_multi.
+
+config COMEDI_DAQBOARD2000
+ tristate "IOtech DAQboard/2000 support"
+ select COMEDI_8255
+ ---help---
+ Enable support for the IOtech DAQboard/2000
+
+ To compile this driver as a module, choose M here: the module will be
+ called daqboard2000.
+
+config COMEDI_JR3_PCI
+ tristate "JR3/PCI force sensor board support"
+ ---help---
+ Enable support for JR3/PCI force sensor boards
+
+ To compile this driver as a module, choose M here: the module will be
+ called jr3_pci.
+
+config COMEDI_KE_COUNTER
+ tristate "Kolter-Electronic PCI Counter 1 card support"
+ ---help---
+ Enable support for Kolter-Electronic PCI Counter 1 cards
+
+ To compile this driver as a module, choose M here: the module will be
+ called ke_counter.
+
+config COMEDI_CB_PCIDAS64
+ tristate "MeasurementComputing PCI-DAS 64xx, 60xx, and 4020 support"
+ select COMEDI_8255
+ ---help---
+ Enable support for ComputerBoards/MeasurementComputing PCI-DAS 64xx,
+ 60xx, and 4020 series with the PLX 9080 PCI controller
+
+ To compile this driver as a module, choose M here: the module will be
+ called cb_pcidas64.
+
+config COMEDI_CB_PCIDAS
+ tristate "MeasurementComputing PCI-DAS support"
+ select COMEDI_8254
+ select COMEDI_8255
+ ---help---
+ Enable support for ComputerBoards/MeasurementComputing PCI-DAS with
+ AMCC S5933 PCIcontroller: PCI-DAS1602/16, PCI-DAS1602/16jr,
+ PCI-DAS1602/12, PCI-DAS1200, PCI-DAS1200jr, PCI-DAS1000, PCI-DAS1001
+ and PCI_DAS1002.
+
+ To compile this driver as a module, choose M here: the module will be
+ called cb_pcidas.
+
+config COMEDI_CB_PCIDDA
+ tristate "MeasurementComputing PCI-DDA series support"
+ select COMEDI_8255
+ ---help---
+ Enable support for ComputerBoards/MeasurementComputing PCI-DDA
+ series: PCI-DDA08/12, PCI-DDA04/12, PCI-DDA02/12, PCI-DDA08/16,
+ PCI-DDA04/16 and PCI-DDA02/16
+
+ To compile this driver as a module, choose M here: the module will be
+ called cb_pcidda.
+
+config COMEDI_CB_PCIMDAS
+ tristate "MeasurementComputing PCIM-DAS1602/16, PCIe-DAS1602/16 support"
+ select COMEDI_8254
+ select COMEDI_8255
+ ---help---
+ Enable support for ComputerBoards/MeasurementComputing PCI Migration
+ series PCIM-DAS1602/16 and PCIe-DAS1602/16.
+
+ To compile this driver as a module, choose M here: the module will be
+ called cb_pcimdas.
+
+config COMEDI_CB_PCIMDDA
+ tristate "MeasurementComputing PCIM-DDA06-16 support"
+ select COMEDI_8255
+ ---help---
+ Enable support for ComputerBoards/MeasurementComputing PCIM-DDA06-16
+
+ To compile this driver as a module, choose M here: the module will be
+ called cb_pcimdda.
+
+config COMEDI_ME4000
+ tristate "Meilhaus ME-4000 support"
+ select COMEDI_8254
+ ---help---
+ Enable support for Meilhaus PCI data acquisition cards
+ ME-4650, ME-4670i, ME-4680, ME-4680i and ME-4680is
+
+ To compile this driver as a module, choose M here: the module will be
+ called me4000.
+
+config COMEDI_ME_DAQ
+ tristate "Meilhaus ME-2000i, ME-2600i, ME-3000vm1 support"
+ ---help---
+ Enable support for Meilhaus PCI data acquisition cards
+ ME-2000i, ME-2600i and ME-3000vm1
+
+ To compile this driver as a module, choose M here: the module will be
+ called me_daq.
+
+config COMEDI_NI_6527
+ tristate "NI 6527 support"
+ ---help---
+ Enable support for the National Instruments 6527 PCI card
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_6527.
+
+config COMEDI_NI_65XX
+ tristate "NI 65xx static dio PCI card support"
+ ---help---
+ Enable support for National Instruments 65xx static dio boards.
+ Supported devices: National Instruments PCI-6509 (ni_65xx),
+ PXI-6509, PCI-6510, PCI-6511, PXI-6511, PCI-6512, PXI-6512, PCI-6513,
+ PXI-6513, PCI-6514, PXI-6514, PCI-6515, PXI-6515, PCI-6516, PCI-6517,
+ PCI-6518, PCI-6519, PCI-6520, PCI-6521, PXI-6521, PCI-6528, PXI-6528
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_65xx.
+
+config COMEDI_NI_660X
+ tristate "NI 660x counter/timer PCI card support"
+ depends on HAS_DMA
+ select COMEDI_NI_TIOCMD
+ ---help---
+ Enable support for National Instruments PCI-6601 (ni_660x), PCI-6602,
+ PXI-6602, PXI-6608 and PXI-6624.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_660x.
+
+config COMEDI_NI_670X
+ tristate "NI 670x PCI card support"
+ ---help---
+ Enable support for National Instruments PCI-6703 and PCI-6704
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_670x.
+
+config COMEDI_NI_LABPC_PCI
+ tristate "NI Lab-PC PCI-1200 support"
+ select COMEDI_NI_LABPC
+ ---help---
+ Enable support for National Instruments Lab-PC PCI-1200.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_labpc_pci.
+
+config COMEDI_NI_PCIDIO
+ tristate "NI PCI-DIO32HS, PCI-6533, PCI-6534 support"
+ depends on HAS_DMA
+ select COMEDI_MITE
+ select COMEDI_8255
+ ---help---
+ Enable support for National Instruments PCI-DIO-32HS, PXI-6533,
+ PCI-6533 and PCI-6534
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_pcidio.
+
+config COMEDI_NI_PCIMIO
+ tristate "NI PCI-MIO-E series and M series support"
+ depends on HAS_DMA
+ select COMEDI_NI_TIOCMD
+ select COMEDI_8255
+ ---help---
+ Enable support for National Instruments PCI-MIO-E series and M series
+ (all boards): PCI-MIO-16XE-10, PXI-6030E, PCI-MIO-16E-1,
+ PCI-MIO-16E-4, PCI-6014, PCI-6040E, PXI-6040E, PCI-6030E, PCI-6031E,
+ PCI-6032E, PCI-6033E, PCI-6071E, PCI-6023E, PCI-6024E, PCI-6025E,
+ PXI-6025E, PCI-6034E, PCI-6035E, PCI-6052E, PCI-6110, PCI-6111,
+ PCI-6220, PCI-6221, PCI-6224, PXI-6224, PCI-6225, PXI-6225, PCI-6229,
+ PCI-6250, PCI-6251, PCIe-6251, PCI-6254, PCI-6259, PCIe-6259,
+ PCI-6280, PCI-6281, PXI-6281, PCI-6284, PCI-6289, PCI-6711, PXI-6711,
+ PCI-6713, PXI-6713, PXI-6071E, PCI-6070E, PXI-6070E, PXI-6052E,
+ PCI-6036E, PCI-6731, PCI-6733, PXI-6733, PCI-6143, PXI-6143
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_pcimio.
+
+config COMEDI_RTD520
+ tristate "Real Time Devices PCI4520/DM7520 support"
+ ---help---
+ Enable support for Real Time Devices PCI4520/DM7520
+
+ To compile this driver as a module, choose M here: the module will be
+ called rtd520.
+
+config COMEDI_S626
+ tristate "Sensoray 626 support"
+ ---help---
+ Enable support for Sensoray 626
+
+ To compile this driver as a module, choose M here: the module will be
+ called s626.
+
+config COMEDI_MITE
+ depends on HAS_DMA
+ tristate
+
+config COMEDI_NI_TIOCMD
+ tristate
+ depends on HAS_DMA
+ select COMEDI_NI_TIO
+ select COMEDI_MITE
+
+endif # COMEDI_PCI_DRIVERS
+
+menuconfig COMEDI_PCMCIA_DRIVERS
+ tristate "Comedi PCMCIA drivers"
+ depends on PCMCIA
+ ---help---
+ Enable support for comedi PCMCIA drivers.
+
+ To compile this support as a module, choose M here: the module will
+ be called comedi_pcmcia.
+
+if COMEDI_PCMCIA_DRIVERS
+
+config COMEDI_CB_DAS16_CS
+ tristate "CB DAS16 series PCMCIA support"
+ select COMEDI_8254
+ ---help---
+ Enable support for the ComputerBoards/MeasurementComputing PCMCIA
+ cards DAS16/16, PCM-DAS16D/12 and PCM-DAS16s/16
+
+ To compile this driver as a module, choose M here: the module will be
+ called cb_das16_cs.
+
+config COMEDI_DAS08_CS
+ tristate "CB DAS08 PCMCIA support"
+ select COMEDI_DAS08
+ ---help---
+ Enable support for the ComputerBoards/MeasurementComputing DAS-08
+ PCMCIA card
+
+ To compile this driver as a module, choose M here: the module will be
+ called das08_cs.
+
+config COMEDI_NI_DAQ_700_CS
+ tristate "NI DAQCard-700 PCMCIA support"
+ ---help---
+ Enable support for the National Instruments PCMCIA DAQCard-700 DIO
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_daq_700.
+
+config COMEDI_NI_DAQ_DIO24_CS
+ tristate "NI DAQ-Card DIO-24 PCMCIA support"
+ select COMEDI_8255
+ ---help---
+ Enable support for the National Instruments PCMCIA DAQ-Card DIO-24
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_daq_dio24.
+
+config COMEDI_NI_LABPC_CS
+ tristate "NI DAQCard-1200 PCMCIA support"
+ select COMEDI_NI_LABPC
+ ---help---
+ Enable support for the National Instruments PCMCIA DAQCard-1200
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_labpc_cs.
+
+config COMEDI_NI_MIO_CS
+ tristate "NI DAQCard E series PCMCIA support"
+ select COMEDI_NI_TIO
+ select COMEDI_8255
+ ---help---
+ Enable support for the National Instruments PCMCIA DAQCard E series
+ DAQCard-ai-16xe-50, DAQCard-ai-16e-4, DAQCard-6062E, DAQCard-6024E
+ and DAQCard-6036E
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_mio_cs.
+
+config COMEDI_QUATECH_DAQP_CS
+ tristate "Quatech DAQP PCMCIA data capture card support"
+ ---help---
+ Enable support for the Quatech DAQP PCMCIA data capture cards
+ DAQP-208 and DAQP-308
+
+ To compile this driver as a module, choose M here: the module will be
+ called quatech_daqp_cs.
+
+endif # COMEDI_PCMCIA_DRIVERS
+
+menuconfig COMEDI_USB_DRIVERS
+ tristate "Comedi USB drivers"
+ depends on USB
+ ---help---
+ Enable support for comedi USB drivers.
+
+ To compile this support as a module, choose M here: the module will
+ be called comedi_usb.
+
+if COMEDI_USB_DRIVERS
+
+config COMEDI_DT9812
+ tristate "DataTranslation DT9812 USB module support"
+ ---help---
+ Enable support for the Data Translation DT9812 USB module
+
+ To compile this driver as a module, choose M here: the module will be
+ called dt9812.
+
+config COMEDI_NI_USB6501
+ tristate "NI USB-6501 support"
+ ---help---
+ Enable support for the National Instruments USB-6501 module.
+
+ The NI USB-6501 is a Full-Speed USB 2.0 (12 Mbit/s) device that
+ provides 24 digital I/O lines channels and one 32-bit counter.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_usb6501.
+
+config COMEDI_USBDUX
+ tristate "ITL USB-DUX-D support"
+ ---help---
+ Enable support for the Incite Technology Ltd USB-DUX-D Board
+
+ To compile this driver as a module, choose M here: the module will be
+ called usbdux.
+
+config COMEDI_USBDUXFAST
+ tristate "ITL USB-DUXfast support"
+ ---help---
+ Enable support for the Incite Technology Ltd USB-DUXfast Board
+
+ To compile this driver as a module, choose M here: the module will be
+ called usbduxfast.
+
+config COMEDI_USBDUXSIGMA
+ tristate "ITL USB-DUXsigma support"
+ ---help---
+ Enable support for the Incite Technology Ltd USB-DUXsigma Board
+
+ To compile this driver as a module, choose M here: the module will be
+ called usbduxsigma.
+
+config COMEDI_VMK80XX
+ tristate "Velleman VM110/VM140 USB Board support"
+ ---help---
+ Build the Velleman USB Board Low-Level Driver supporting the
+ K8055/K8061 aka VM110/VM140 devices
+
+ To compile this driver as a module, choose M here: the module will be
+ called vmk80xx.
+
+endif # COMEDI_USB_DRIVERS
+
+config COMEDI_8254
+ tristate
+
+config COMEDI_8255
+ tristate "Generic 8255 support"
+ ---help---
+ Enable generic 8255 support.
+
+ You should enable compilation this driver if you plan to use a board
+ that has an 8255 chip. For multifunction boards, the main driver will
+ configure the 8255 subdevice automatically.
+
+ Note that most PCI based 8255 boards use the 8255_pci driver as a
+ wrapper around this driver.
+
+ To compile this driver as a module, choose M here: the module will be
+ called 8255.
+
+config COMEDI_KCOMEDILIB
+ tristate "Comedi kcomedilib"
+ ---help---
+ Build the kcomedilib.
+
+ This is a kernel module used to open and manipulate Comedi devices
+ from within kernel code. It is currently only used by the
+ comedi_bond driver, and its functionality has been stripped down to
+ the needs of that driver, so is currently not very useful for
+ anything else.
+
+ To compile kcomedilib as a module, choose M here: the module will be
+ called kcomedilib.
+
+config COMEDI_AMPLC_DIO200
+ select COMEDI_8254
+ tristate
+
+config COMEDI_AMPLC_PC236
+ tristate
+ select COMEDI_8255
+
+config COMEDI_DAS08
+ tristate
+ select COMEDI_8254
+ select COMEDI_8255
+
+config COMEDI_ISADMA
+ tristate
+
+config COMEDI_NI_LABPC
+ tristate
+ select COMEDI_8254
+ select COMEDI_8255
+
+config COMEDI_NI_LABPC_ISADMA
+ tristate
+ select COMEDI_ISADMA
+
+config COMEDI_NI_TIO
+ tristate
+
+endif # COMEDI
diff --git a/drivers/staging/comedi/Makefile b/drivers/staging/comedi/Makefile
new file mode 100644
index 000000000..7f9dfb392
--- /dev/null
+++ b/drivers/staging/comedi/Makefile
@@ -0,0 +1,15 @@
+ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG
+
+comedi-y := comedi_fops.o range.o drivers.o \
+ comedi_buf.o
+comedi-$(CONFIG_PROC_FS) += proc.o
+comedi-$(CONFIG_COMPAT) += comedi_compat32.o
+
+obj-$(CONFIG_COMEDI_PCI_DRIVERS) += comedi_pci.o
+obj-$(CONFIG_COMEDI_PCMCIA_DRIVERS) += comedi_pcmcia.o
+obj-$(CONFIG_COMEDI_USB_DRIVERS) += comedi_usb.o
+
+obj-$(CONFIG_COMEDI) += comedi.o
+
+obj-$(CONFIG_COMEDI) += kcomedilib/
+obj-$(CONFIG_COMEDI) += drivers/
diff --git a/drivers/staging/comedi/TODO b/drivers/staging/comedi/TODO
new file mode 100644
index 000000000..b68fbdb5e
--- /dev/null
+++ b/drivers/staging/comedi/TODO
@@ -0,0 +1,11 @@
+TODO:
+ - checkpatch.pl cleanups
+ - Lindent
+ - remove all wrappers
+ - audit userspace interface
+ - cleanup the individual comedi drivers as well
+
+Please send patches to Greg Kroah-Hartman <greg@kroah.com> and
+copy:
+ Ian Abbott <abbotti@mev.co.uk>
+ H Hartley Sweeten <hsweeten@visionengravers.com>
diff --git a/drivers/staging/comedi/comedi.h b/drivers/staging/comedi/comedi.h
new file mode 100644
index 000000000..745574077
--- /dev/null
+++ b/drivers/staging/comedi/comedi.h
@@ -0,0 +1,937 @@
+/*
+ include/comedi.h (installed as /usr/include/comedi.h)
+ header file for comedi
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998-2001 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _COMEDI_H
+#define _COMEDI_H
+
+#define COMEDI_MAJORVERSION 0
+#define COMEDI_MINORVERSION 7
+#define COMEDI_MICROVERSION 76
+#define VERSION "0.7.76"
+
+/* comedi's major device number */
+#define COMEDI_MAJOR 98
+
+/*
+ maximum number of minor devices. This can be increased, although
+ kernel structures are currently statically allocated, thus you
+ don't want this to be much more than you actually use.
+ */
+#define COMEDI_NDEVICES 16
+
+/* number of config options in the config structure */
+#define COMEDI_NDEVCONFOPTS 32
+
+/*
+ * NOTE: 'comedi_config --init-data' is deprecated
+ *
+ * The following indexes in the config options were used by
+ * comedi_config to pass firmware blobs from user space to the
+ * comedi drivers. The request_firmware() hotplug interface is
+ * now used by all comedi drivers instead.
+ */
+
+/* length of nth chunk of firmware data -*/
+#define COMEDI_DEVCONF_AUX_DATA3_LENGTH 25
+#define COMEDI_DEVCONF_AUX_DATA2_LENGTH 26
+#define COMEDI_DEVCONF_AUX_DATA1_LENGTH 27
+#define COMEDI_DEVCONF_AUX_DATA0_LENGTH 28
+/* most significant 32 bits of pointer address (if needed) */
+#define COMEDI_DEVCONF_AUX_DATA_HI 29
+/* least significant 32 bits of pointer address */
+#define COMEDI_DEVCONF_AUX_DATA_LO 30
+#define COMEDI_DEVCONF_AUX_DATA_LENGTH 31 /* total data length */
+
+/* max length of device and driver names */
+#define COMEDI_NAMELEN 20
+
+/* packs and unpacks a channel/range number */
+
+#define CR_PACK(chan, rng, aref) \
+ ((((aref)&0x3)<<24) | (((rng)&0xff)<<16) | (chan))
+#define CR_PACK_FLAGS(chan, range, aref, flags) \
+ (CR_PACK(chan, range, aref) | ((flags) & CR_FLAGS_MASK))
+
+#define CR_CHAN(a) ((a)&0xffff)
+#define CR_RANGE(a) (((a)>>16)&0xff)
+#define CR_AREF(a) (((a)>>24)&0x03)
+
+#define CR_FLAGS_MASK 0xfc000000
+#define CR_ALT_FILTER (1<<26)
+#define CR_DITHER CR_ALT_FILTER
+#define CR_DEGLITCH CR_ALT_FILTER
+#define CR_ALT_SOURCE (1<<27)
+#define CR_EDGE (1<<30)
+#define CR_INVERT (1<<31)
+
+#define AREF_GROUND 0x00 /* analog ref = analog ground */
+#define AREF_COMMON 0x01 /* analog ref = analog common */
+#define AREF_DIFF 0x02 /* analog ref = differential */
+#define AREF_OTHER 0x03 /* analog ref = other (undefined) */
+
+/* counters -- these are arbitrary values */
+#define GPCT_RESET 0x0001
+#define GPCT_SET_SOURCE 0x0002
+#define GPCT_SET_GATE 0x0004
+#define GPCT_SET_DIRECTION 0x0008
+#define GPCT_SET_OPERATION 0x0010
+#define GPCT_ARM 0x0020
+#define GPCT_DISARM 0x0040
+#define GPCT_GET_INT_CLK_FRQ 0x0080
+
+#define GPCT_INT_CLOCK 0x0001
+#define GPCT_EXT_PIN 0x0002
+#define GPCT_NO_GATE 0x0004
+#define GPCT_UP 0x0008
+#define GPCT_DOWN 0x0010
+#define GPCT_HWUD 0x0020
+#define GPCT_SIMPLE_EVENT 0x0040
+#define GPCT_SINGLE_PERIOD 0x0080
+#define GPCT_SINGLE_PW 0x0100
+#define GPCT_CONT_PULSE_OUT 0x0200
+#define GPCT_SINGLE_PULSE_OUT 0x0400
+
+/* instructions */
+
+#define INSN_MASK_WRITE 0x8000000
+#define INSN_MASK_READ 0x4000000
+#define INSN_MASK_SPECIAL 0x2000000
+
+#define INSN_READ (0 | INSN_MASK_READ)
+#define INSN_WRITE (1 | INSN_MASK_WRITE)
+#define INSN_BITS (2 | INSN_MASK_READ|INSN_MASK_WRITE)
+#define INSN_CONFIG (3 | INSN_MASK_READ|INSN_MASK_WRITE)
+#define INSN_GTOD (4 | INSN_MASK_READ|INSN_MASK_SPECIAL)
+#define INSN_WAIT (5 | INSN_MASK_WRITE|INSN_MASK_SPECIAL)
+#define INSN_INTTRIG (6 | INSN_MASK_WRITE|INSN_MASK_SPECIAL)
+
+/* trigger flags */
+/* These flags are used in comedi_trig structures */
+
+#define TRIG_DITHER 0x0002 /* enable dithering */
+#define TRIG_DEGLITCH 0x0004 /* enable deglitching */
+#define TRIG_CONFIG 0x0010 /* perform configuration, not triggering */
+
+/* command flags */
+/* These flags are used in comedi_cmd structures */
+
+#define CMDF_BOGUS 0x00000001 /* do the motions */
+
+/* try to use a real-time interrupt while performing command */
+#define CMDF_PRIORITY 0x00000008
+
+/* wake up on end-of-scan events */
+#define CMDF_WAKE_EOS 0x00000020
+
+#define CMDF_WRITE 0x00000040
+
+#define CMDF_RAWDATA 0x00000080
+
+/* timer rounding definitions */
+#define CMDF_ROUND_MASK 0x00030000
+#define CMDF_ROUND_NEAREST 0x00000000
+#define CMDF_ROUND_DOWN 0x00010000
+#define CMDF_ROUND_UP 0x00020000
+#define CMDF_ROUND_UP_NEXT 0x00030000
+
+#define COMEDI_EV_START 0x00040000
+#define COMEDI_EV_SCAN_BEGIN 0x00080000
+#define COMEDI_EV_CONVERT 0x00100000
+#define COMEDI_EV_SCAN_END 0x00200000
+#define COMEDI_EV_STOP 0x00400000
+
+/* compatibility definitions */
+#define TRIG_BOGUS CMDF_BOGUS
+#define TRIG_RT CMDF_PRIORITY
+#define TRIG_WAKE_EOS CMDF_WAKE_EOS
+#define TRIG_WRITE CMDF_WRITE
+#define TRIG_ROUND_MASK CMDF_ROUND_MASK
+#define TRIG_ROUND_NEAREST CMDF_ROUND_NEAREST
+#define TRIG_ROUND_DOWN CMDF_ROUND_DOWN
+#define TRIG_ROUND_UP CMDF_ROUND_UP
+#define TRIG_ROUND_UP_NEXT CMDF_ROUND_UP_NEXT
+
+/* trigger sources */
+
+#define TRIG_ANY 0xffffffff
+#define TRIG_INVALID 0x00000000
+
+#define TRIG_NONE 0x00000001 /* never trigger */
+#define TRIG_NOW 0x00000002 /* trigger now + N ns */
+#define TRIG_FOLLOW 0x00000004 /* trigger on next lower level trig */
+#define TRIG_TIME 0x00000008 /* trigger at time N ns */
+#define TRIG_TIMER 0x00000010 /* trigger at rate N ns */
+#define TRIG_COUNT 0x00000020 /* trigger when count reaches N */
+#define TRIG_EXT 0x00000040 /* trigger on external signal N */
+#define TRIG_INT 0x00000080 /* trigger on comedi-internal signal N */
+#define TRIG_OTHER 0x00000100 /* driver defined */
+
+/* subdevice flags */
+
+#define SDF_BUSY 0x0001 /* device is busy */
+#define SDF_BUSY_OWNER 0x0002 /* device is busy with your job */
+#define SDF_LOCKED 0x0004 /* subdevice is locked */
+#define SDF_LOCK_OWNER 0x0008 /* you own lock */
+#define SDF_MAXDATA 0x0010 /* maxdata depends on channel */
+#define SDF_FLAGS 0x0020 /* flags depend on channel */
+#define SDF_RANGETYPE 0x0040 /* range type depends on channel */
+#define SDF_MODE0 0x0080 /* can do mode 0 */
+#define SDF_MODE1 0x0100 /* can do mode 1 */
+#define SDF_MODE2 0x0200 /* can do mode 2 */
+#define SDF_MODE3 0x0400 /* can do mode 3 */
+#define SDF_MODE4 0x0800 /* can do mode 4 */
+#define SDF_CMD 0x1000 /* can do commands (deprecated) */
+#define SDF_SOFT_CALIBRATED 0x2000 /* subdevice uses software calibration */
+#define SDF_CMD_WRITE 0x4000 /* can do output commands */
+#define SDF_CMD_READ 0x8000 /* can do input commands */
+
+/* subdevice can be read (e.g. analog input) */
+#define SDF_READABLE 0x00010000
+/* subdevice can be written (e.g. analog output) */
+#define SDF_WRITABLE 0x00020000
+#define SDF_WRITEABLE SDF_WRITABLE /* spelling error in API */
+/* subdevice does not have externally visible lines */
+#define SDF_INTERNAL 0x00040000
+#define SDF_GROUND 0x00100000 /* can do aref=ground */
+#define SDF_COMMON 0x00200000 /* can do aref=common */
+#define SDF_DIFF 0x00400000 /* can do aref=diff */
+#define SDF_OTHER 0x00800000 /* can do aref=other */
+#define SDF_DITHER 0x01000000 /* can do dithering */
+#define SDF_DEGLITCH 0x02000000 /* can do deglitching */
+#define SDF_MMAP 0x04000000 /* can do mmap() */
+#define SDF_RUNNING 0x08000000 /* subdevice is acquiring data */
+#define SDF_LSAMPL 0x10000000 /* subdevice uses 32-bit samples */
+#define SDF_PACKED 0x20000000 /* subdevice can do packed DIO */
+/* re recyle these flags for PWM */
+#define SDF_PWM_COUNTER SDF_MODE0 /* PWM can automatically switch off */
+#define SDF_PWM_HBRIDGE SDF_MODE1 /* PWM is signed (H-bridge) */
+
+/* subdevice types */
+
+enum comedi_subdevice_type {
+ COMEDI_SUBD_UNUSED, /* unused by driver */
+ COMEDI_SUBD_AI, /* analog input */
+ COMEDI_SUBD_AO, /* analog output */
+ COMEDI_SUBD_DI, /* digital input */
+ COMEDI_SUBD_DO, /* digital output */
+ COMEDI_SUBD_DIO, /* digital input/output */
+ COMEDI_SUBD_COUNTER, /* counter */
+ COMEDI_SUBD_TIMER, /* timer */
+ COMEDI_SUBD_MEMORY, /* memory, EEPROM, DPRAM */
+ COMEDI_SUBD_CALIB, /* calibration DACs */
+ COMEDI_SUBD_PROC, /* processor, DSP */
+ COMEDI_SUBD_SERIAL, /* serial IO */
+ COMEDI_SUBD_PWM /* PWM */
+};
+
+/* configuration instructions */
+
+enum configuration_ids {
+ INSN_CONFIG_DIO_INPUT = 0,
+ INSN_CONFIG_DIO_OUTPUT = 1,
+ INSN_CONFIG_DIO_OPENDRAIN = 2,
+ INSN_CONFIG_ANALOG_TRIG = 16,
+/* INSN_CONFIG_WAVEFORM = 17, */
+/* INSN_CONFIG_TRIG = 18, */
+/* INSN_CONFIG_COUNTER = 19, */
+ INSN_CONFIG_ALT_SOURCE = 20,
+ INSN_CONFIG_DIGITAL_TRIG = 21,
+ INSN_CONFIG_BLOCK_SIZE = 22,
+ INSN_CONFIG_TIMER_1 = 23,
+ INSN_CONFIG_FILTER = 24,
+ INSN_CONFIG_CHANGE_NOTIFY = 25,
+
+ INSN_CONFIG_SERIAL_CLOCK = 26, /*ALPHA*/
+ INSN_CONFIG_BIDIRECTIONAL_DATA = 27,
+ INSN_CONFIG_DIO_QUERY = 28,
+ INSN_CONFIG_PWM_OUTPUT = 29,
+ INSN_CONFIG_GET_PWM_OUTPUT = 30,
+ INSN_CONFIG_ARM = 31,
+ INSN_CONFIG_DISARM = 32,
+ INSN_CONFIG_GET_COUNTER_STATUS = 33,
+ INSN_CONFIG_RESET = 34,
+ /* Use CTR as single pulsegenerator */
+ INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR = 1001,
+ /* Use CTR as pulsetraingenerator */
+ INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR = 1002,
+ /* Use the counter as encoder */
+ INSN_CONFIG_GPCT_QUADRATURE_ENCODER = 1003,
+ INSN_CONFIG_SET_GATE_SRC = 2001, /* Set gate source */
+ INSN_CONFIG_GET_GATE_SRC = 2002, /* Get gate source */
+ /* Set master clock source */
+ INSN_CONFIG_SET_CLOCK_SRC = 2003,
+ INSN_CONFIG_GET_CLOCK_SRC = 2004, /* Get master clock source */
+ INSN_CONFIG_SET_OTHER_SRC = 2005, /* Set other source */
+ /* INSN_CONFIG_GET_OTHER_SRC = 2006,*//* Get other source */
+ /* Get size in bytes of subdevice's on-board fifos used during
+ * streaming input/output */
+ INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE = 2006,
+ INSN_CONFIG_SET_COUNTER_MODE = 4097,
+ /* INSN_CONFIG_8254_SET_MODE is deprecated */
+ INSN_CONFIG_8254_SET_MODE = INSN_CONFIG_SET_COUNTER_MODE,
+ INSN_CONFIG_8254_READ_STATUS = 4098,
+ INSN_CONFIG_SET_ROUTING = 4099,
+ INSN_CONFIG_GET_ROUTING = 4109,
+ /* PWM */
+ INSN_CONFIG_PWM_SET_PERIOD = 5000, /* sets frequency */
+ INSN_CONFIG_PWM_GET_PERIOD = 5001, /* gets frequency */
+ INSN_CONFIG_GET_PWM_STATUS = 5002, /* is it running? */
+ /* sets H bridge: duty cycle and sign bit for a relay at the
+ * same time */
+ INSN_CONFIG_PWM_SET_H_BRIDGE = 5003,
+ /* gets H bridge data: duty cycle and the sign bit */
+ INSN_CONFIG_PWM_GET_H_BRIDGE = 5004
+};
+
+/*
+ * Settings for INSN_CONFIG_DIGITAL_TRIG:
+ * data[0] = INSN_CONFIG_DIGITAL_TRIG
+ * data[1] = trigger ID
+ * data[2] = configuration operation
+ * data[3] = configuration parameter 1
+ * data[4] = configuration parameter 2
+ * data[5] = configuration parameter 3
+ *
+ * operation parameter 1 parameter 2 parameter 3
+ * --------------------------------- ----------- ----------- -----------
+ * COMEDI_DIGITAL_TRIG_DISABLE
+ * COMEDI_DIGITAL_TRIG_ENABLE_EDGES left-shift rising-edges falling-edges
+ * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS left-shift high-levels low-levels
+ *
+ * COMEDI_DIGITAL_TRIG_DISABLE returns the trigger to its default, inactive,
+ * unconfigured state.
+ *
+ * COMEDI_DIGITAL_TRIG_ENABLE_EDGES sets the rising and/or falling edge inputs
+ * that each can fire the trigger.
+ *
+ * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS sets a combination of high and/or low
+ * level inputs that can fire the trigger.
+ *
+ * "left-shift" is useful if the trigger has more than 32 inputs to specify the
+ * first input for this configuration.
+ *
+ * Some sequences of INSN_CONFIG_DIGITAL_TRIG instructions may have a (partly)
+ * accumulative effect, depending on the low-level driver. This is useful
+ * when setting up a trigger that has more than 32 inputs or has a combination
+ * of edge and level triggered inputs.
+ */
+enum comedi_digital_trig_op {
+ COMEDI_DIGITAL_TRIG_DISABLE = 0,
+ COMEDI_DIGITAL_TRIG_ENABLE_EDGES = 1,
+ COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = 2
+};
+
+enum comedi_io_direction {
+ COMEDI_INPUT = 0,
+ COMEDI_OUTPUT = 1,
+ COMEDI_OPENDRAIN = 2
+};
+
+enum comedi_support_level {
+ COMEDI_UNKNOWN_SUPPORT = 0,
+ COMEDI_SUPPORTED,
+ COMEDI_UNSUPPORTED
+};
+
+/* ioctls */
+
+#define CIO 'd'
+#define COMEDI_DEVCONFIG _IOW(CIO, 0, struct comedi_devconfig)
+#define COMEDI_DEVINFO _IOR(CIO, 1, struct comedi_devinfo)
+#define COMEDI_SUBDINFO _IOR(CIO, 2, struct comedi_subdinfo)
+#define COMEDI_CHANINFO _IOR(CIO, 3, struct comedi_chaninfo)
+#define COMEDI_TRIG _IOWR(CIO, 4, comedi_trig)
+#define COMEDI_LOCK _IO(CIO, 5)
+#define COMEDI_UNLOCK _IO(CIO, 6)
+#define COMEDI_CANCEL _IO(CIO, 7)
+#define COMEDI_RANGEINFO _IOR(CIO, 8, struct comedi_rangeinfo)
+#define COMEDI_CMD _IOR(CIO, 9, struct comedi_cmd)
+#define COMEDI_CMDTEST _IOR(CIO, 10, struct comedi_cmd)
+#define COMEDI_INSNLIST _IOR(CIO, 11, struct comedi_insnlist)
+#define COMEDI_INSN _IOR(CIO, 12, struct comedi_insn)
+#define COMEDI_BUFCONFIG _IOR(CIO, 13, struct comedi_bufconfig)
+#define COMEDI_BUFINFO _IOWR(CIO, 14, struct comedi_bufinfo)
+#define COMEDI_POLL _IO(CIO, 15)
+#define COMEDI_SETRSUBD _IO(CIO, 16)
+#define COMEDI_SETWSUBD _IO(CIO, 17)
+
+/* structures */
+
+struct comedi_trig {
+ unsigned int subdev; /* subdevice */
+ unsigned int mode; /* mode */
+ unsigned int flags;
+ unsigned int n_chan; /* number of channels */
+ unsigned int *chanlist; /* channel/range list */
+ short *data; /* data list, size depends on subd flags */
+ unsigned int n; /* number of scans */
+ unsigned int trigsrc;
+ unsigned int trigvar;
+ unsigned int trigvar1;
+ unsigned int data_len;
+ unsigned int unused[3];
+};
+
+struct comedi_insn {
+ unsigned int insn;
+ unsigned int n;
+ unsigned int __user *data;
+ unsigned int subdev;
+ unsigned int chanspec;
+ unsigned int unused[3];
+};
+
+struct comedi_insnlist {
+ unsigned int n_insns;
+ struct comedi_insn __user *insns;
+};
+
+struct comedi_cmd {
+ unsigned int subdev;
+ unsigned int flags;
+
+ unsigned int start_src;
+ unsigned int start_arg;
+
+ unsigned int scan_begin_src;
+ unsigned int scan_begin_arg;
+
+ unsigned int convert_src;
+ unsigned int convert_arg;
+
+ unsigned int scan_end_src;
+ unsigned int scan_end_arg;
+
+ unsigned int stop_src;
+ unsigned int stop_arg;
+
+ unsigned int *chanlist; /* channel/range list */
+ unsigned int chanlist_len;
+
+ short __user *data; /* data list, size depends on subd flags */
+ unsigned int data_len;
+};
+
+struct comedi_chaninfo {
+ unsigned int subdev;
+ unsigned int __user *maxdata_list;
+ unsigned int __user *flaglist;
+ unsigned int __user *rangelist;
+ unsigned int unused[4];
+};
+
+struct comedi_rangeinfo {
+ unsigned int range_type;
+ void __user *range_ptr;
+};
+
+struct comedi_krange {
+ int min; /* fixed point, multiply by 1e-6 */
+ int max; /* fixed point, multiply by 1e-6 */
+ unsigned int flags;
+};
+
+struct comedi_subdinfo {
+ unsigned int type;
+ unsigned int n_chan;
+ unsigned int subd_flags;
+ unsigned int timer_type;
+ unsigned int len_chanlist;
+ unsigned int maxdata;
+ unsigned int flags; /* channel flags */
+ unsigned int range_type; /* lookup in kernel */
+ unsigned int settling_time_0;
+ /* see support_level enum for values */
+ unsigned insn_bits_support;
+ unsigned int unused[8];
+};
+
+struct comedi_devinfo {
+ unsigned int version_code;
+ unsigned int n_subdevs;
+ char driver_name[COMEDI_NAMELEN];
+ char board_name[COMEDI_NAMELEN];
+ int read_subdevice;
+ int write_subdevice;
+ int unused[30];
+};
+
+struct comedi_devconfig {
+ char board_name[COMEDI_NAMELEN];
+ int options[COMEDI_NDEVCONFOPTS];
+};
+
+struct comedi_bufconfig {
+ unsigned int subdevice;
+ unsigned int flags;
+
+ unsigned int maximum_size;
+ unsigned int size;
+
+ unsigned int unused[4];
+};
+
+struct comedi_bufinfo {
+ unsigned int subdevice;
+ unsigned int bytes_read;
+
+ unsigned int buf_write_ptr;
+ unsigned int buf_read_ptr;
+ unsigned int buf_write_count;
+ unsigned int buf_read_count;
+
+ unsigned int bytes_written;
+
+ unsigned int unused[4];
+};
+
+/* range stuff */
+
+#define __RANGE(a, b) ((((a)&0xffff)<<16)|((b)&0xffff))
+
+#define RANGE_OFFSET(a) (((a)>>16)&0xffff)
+#define RANGE_LENGTH(b) ((b)&0xffff)
+
+#define RF_UNIT(flags) ((flags)&0xff)
+#define RF_EXTERNAL (1<<8)
+
+#define UNIT_volt 0
+#define UNIT_mA 1
+#define UNIT_none 2
+
+#define COMEDI_MIN_SPEED ((unsigned int)0xffffffff)
+
+/**********************************************************/
+/* everything after this line is ALPHA */
+/**********************************************************/
+
+/*
+ 8254 specific configuration.
+
+ It supports two config commands:
+
+ 0 ID: INSN_CONFIG_SET_COUNTER_MODE
+ 1 8254 Mode
+ I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
+ OR'ed with:
+ I8254_BCD, I8254_BINARY
+
+ 0 ID: INSN_CONFIG_8254_READ_STATUS
+ 1 <-- Status byte returned here.
+ B7 = Output
+ B6 = NULL Count
+ B5 - B0 Current mode.
+
+*/
+
+enum i8254_mode {
+ I8254_MODE0 = (0 << 1), /* Interrupt on terminal count */
+ I8254_MODE1 = (1 << 1), /* Hardware retriggerable one-shot */
+ I8254_MODE2 = (2 << 1), /* Rate generator */
+ I8254_MODE3 = (3 << 1), /* Square wave mode */
+ I8254_MODE4 = (4 << 1), /* Software triggered strobe */
+ I8254_MODE5 = (5 << 1), /* Hardware triggered strobe
+ * (retriggerable) */
+ I8254_BCD = 1, /* use binary-coded decimal instead of binary
+ * (pretty useless) */
+ I8254_BINARY = 0
+};
+
+#define NI_USUAL_PFI_SELECT(x) (((x) < 10) ? (0x1 + (x)) : (0xb + (x)))
+#define NI_USUAL_RTSI_SELECT(x) (((x) < 7) ? (0xb + (x)) : 0x1b)
+
+/* mode bits for NI general-purpose counters, set with
+ * INSN_CONFIG_SET_COUNTER_MODE */
+#define NI_GPCT_COUNTING_MODE_SHIFT 16
+#define NI_GPCT_INDEX_PHASE_BITSHIFT 20
+#define NI_GPCT_COUNTING_DIRECTION_SHIFT 24
+enum ni_gpct_mode_bits {
+ NI_GPCT_GATE_ON_BOTH_EDGES_BIT = 0x4,
+ NI_GPCT_EDGE_GATE_MODE_MASK = 0x18,
+ NI_GPCT_EDGE_GATE_STARTS_STOPS_BITS = 0x0,
+ NI_GPCT_EDGE_GATE_STOPS_STARTS_BITS = 0x8,
+ NI_GPCT_EDGE_GATE_STARTS_BITS = 0x10,
+ NI_GPCT_EDGE_GATE_NO_STARTS_NO_STOPS_BITS = 0x18,
+ NI_GPCT_STOP_MODE_MASK = 0x60,
+ NI_GPCT_STOP_ON_GATE_BITS = 0x00,
+ NI_GPCT_STOP_ON_GATE_OR_TC_BITS = 0x20,
+ NI_GPCT_STOP_ON_GATE_OR_SECOND_TC_BITS = 0x40,
+ NI_GPCT_LOAD_B_SELECT_BIT = 0x80,
+ NI_GPCT_OUTPUT_MODE_MASK = 0x300,
+ NI_GPCT_OUTPUT_TC_PULSE_BITS = 0x100,
+ NI_GPCT_OUTPUT_TC_TOGGLE_BITS = 0x200,
+ NI_GPCT_OUTPUT_TC_OR_GATE_TOGGLE_BITS = 0x300,
+ NI_GPCT_HARDWARE_DISARM_MASK = 0xc00,
+ NI_GPCT_NO_HARDWARE_DISARM_BITS = 0x000,
+ NI_GPCT_DISARM_AT_TC_BITS = 0x400,
+ NI_GPCT_DISARM_AT_GATE_BITS = 0x800,
+ NI_GPCT_DISARM_AT_TC_OR_GATE_BITS = 0xc00,
+ NI_GPCT_LOADING_ON_TC_BIT = 0x1000,
+ NI_GPCT_LOADING_ON_GATE_BIT = 0x4000,
+ NI_GPCT_COUNTING_MODE_MASK = 0x7 << NI_GPCT_COUNTING_MODE_SHIFT,
+ NI_GPCT_COUNTING_MODE_NORMAL_BITS =
+ 0x0 << NI_GPCT_COUNTING_MODE_SHIFT,
+ NI_GPCT_COUNTING_MODE_QUADRATURE_X1_BITS =
+ 0x1 << NI_GPCT_COUNTING_MODE_SHIFT,
+ NI_GPCT_COUNTING_MODE_QUADRATURE_X2_BITS =
+ 0x2 << NI_GPCT_COUNTING_MODE_SHIFT,
+ NI_GPCT_COUNTING_MODE_QUADRATURE_X4_BITS =
+ 0x3 << NI_GPCT_COUNTING_MODE_SHIFT,
+ NI_GPCT_COUNTING_MODE_TWO_PULSE_BITS =
+ 0x4 << NI_GPCT_COUNTING_MODE_SHIFT,
+ NI_GPCT_COUNTING_MODE_SYNC_SOURCE_BITS =
+ 0x6 << NI_GPCT_COUNTING_MODE_SHIFT,
+ NI_GPCT_INDEX_PHASE_MASK = 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+ NI_GPCT_INDEX_PHASE_LOW_A_LOW_B_BITS =
+ 0x0 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+ NI_GPCT_INDEX_PHASE_LOW_A_HIGH_B_BITS =
+ 0x1 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+ NI_GPCT_INDEX_PHASE_HIGH_A_LOW_B_BITS =
+ 0x2 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+ NI_GPCT_INDEX_PHASE_HIGH_A_HIGH_B_BITS =
+ 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+ NI_GPCT_INDEX_ENABLE_BIT = 0x400000,
+ NI_GPCT_COUNTING_DIRECTION_MASK =
+ 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+ NI_GPCT_COUNTING_DIRECTION_DOWN_BITS =
+ 0x00 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+ NI_GPCT_COUNTING_DIRECTION_UP_BITS =
+ 0x1 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+ NI_GPCT_COUNTING_DIRECTION_HW_UP_DOWN_BITS =
+ 0x2 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+ NI_GPCT_COUNTING_DIRECTION_HW_GATE_BITS =
+ 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+ NI_GPCT_RELOAD_SOURCE_MASK = 0xc000000,
+ NI_GPCT_RELOAD_SOURCE_FIXED_BITS = 0x0,
+ NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS = 0x4000000,
+ NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS = 0x8000000,
+ NI_GPCT_OR_GATE_BIT = 0x10000000,
+ NI_GPCT_INVERT_OUTPUT_BIT = 0x20000000
+};
+
+/* Bits for setting a clock source with
+ * INSN_CONFIG_SET_CLOCK_SRC when using NI general-purpose counters. */
+enum ni_gpct_clock_source_bits {
+ NI_GPCT_CLOCK_SRC_SELECT_MASK = 0x3f,
+ NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS = 0x0,
+ NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS = 0x1,
+ NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS = 0x2,
+ NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS = 0x3,
+ NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS = 0x4,
+ NI_GPCT_NEXT_TC_CLOCK_SRC_BITS = 0x5,
+ /* NI 660x-specific */
+ NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS = 0x6,
+ NI_GPCT_PXI10_CLOCK_SRC_BITS = 0x7,
+ NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS = 0x8,
+ NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS = 0x9,
+ NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK = 0x30000000,
+ NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS = 0x0,
+ /* divide source by 2 */
+ NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS = 0x10000000,
+ /* divide source by 8 */
+ NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS = 0x20000000,
+ NI_GPCT_INVERT_CLOCK_SRC_BIT = 0x80000000
+};
+
+/* NI 660x-specific */
+#define NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(x) (0x10 + (x))
+
+#define NI_GPCT_RTSI_CLOCK_SRC_BITS(x) (0x18 + (x))
+
+/* no pfi on NI 660x */
+#define NI_GPCT_PFI_CLOCK_SRC_BITS(x) (0x20 + (x))
+
+/* Possibilities for setting a gate source with
+INSN_CONFIG_SET_GATE_SRC when using NI general-purpose counters.
+May be bitwise-or'd with CR_EDGE or CR_INVERT. */
+enum ni_gpct_gate_select {
+ /* m-series gates */
+ NI_GPCT_TIMESTAMP_MUX_GATE_SELECT = 0x0,
+ NI_GPCT_AI_START2_GATE_SELECT = 0x12,
+ NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT = 0x13,
+ NI_GPCT_NEXT_OUT_GATE_SELECT = 0x14,
+ NI_GPCT_AI_START1_GATE_SELECT = 0x1c,
+ NI_GPCT_NEXT_SOURCE_GATE_SELECT = 0x1d,
+ NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT = 0x1e,
+ NI_GPCT_LOGIC_LOW_GATE_SELECT = 0x1f,
+ /* more gates for 660x */
+ NI_GPCT_SOURCE_PIN_i_GATE_SELECT = 0x100,
+ NI_GPCT_GATE_PIN_i_GATE_SELECT = 0x101,
+ /* more gates for 660x "second gate" */
+ NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT = 0x201,
+ NI_GPCT_SELECTED_GATE_GATE_SELECT = 0x21e,
+ /* m-series "second gate" sources are unknown,
+ * we should add them here with an offset of 0x300 when
+ * known. */
+ NI_GPCT_DISABLED_GATE_SELECT = 0x8000,
+};
+
+#define NI_GPCT_GATE_PIN_GATE_SELECT(x) (0x102 + (x))
+#define NI_GPCT_RTSI_GATE_SELECT(x) NI_USUAL_RTSI_SELECT(x)
+#define NI_GPCT_PFI_GATE_SELECT(x) NI_USUAL_PFI_SELECT(x)
+#define NI_GPCT_UP_DOWN_PIN_GATE_SELECT(x) (0x202 + (x))
+
+/* Possibilities for setting a source with
+INSN_CONFIG_SET_OTHER_SRC when using NI general-purpose counters. */
+enum ni_gpct_other_index {
+ NI_GPCT_SOURCE_ENCODER_A,
+ NI_GPCT_SOURCE_ENCODER_B,
+ NI_GPCT_SOURCE_ENCODER_Z
+};
+
+enum ni_gpct_other_select {
+ /* m-series gates */
+ /* Still unknown, probably only need NI_GPCT_PFI_OTHER_SELECT */
+ NI_GPCT_DISABLED_OTHER_SELECT = 0x8000,
+};
+
+#define NI_GPCT_PFI_OTHER_SELECT(x) NI_USUAL_PFI_SELECT(x)
+
+/* start sources for ni general-purpose counters for use with
+INSN_CONFIG_ARM */
+enum ni_gpct_arm_source {
+ NI_GPCT_ARM_IMMEDIATE = 0x0,
+ NI_GPCT_ARM_PAIRED_IMMEDIATE = 0x1, /* Start both the counter
+ * and the adjacent paired
+ * counter simultaneously */
+ /* NI doesn't document bits for selecting hardware arm triggers.
+ * If the NI_GPCT_ARM_UNKNOWN bit is set, we will pass the least
+ * significant bits (3 bits for 660x or 5 bits for m-series)
+ * through to the hardware. This will at least allow someone to
+ * figure out what the bits do later. */
+ NI_GPCT_ARM_UNKNOWN = 0x1000,
+};
+
+/* digital filtering options for ni 660x for use with INSN_CONFIG_FILTER. */
+enum ni_gpct_filter_select {
+ NI_GPCT_FILTER_OFF = 0x0,
+ NI_GPCT_FILTER_TIMEBASE_3_SYNC = 0x1,
+ NI_GPCT_FILTER_100x_TIMEBASE_1 = 0x2,
+ NI_GPCT_FILTER_20x_TIMEBASE_1 = 0x3,
+ NI_GPCT_FILTER_10x_TIMEBASE_1 = 0x4,
+ NI_GPCT_FILTER_2x_TIMEBASE_1 = 0x5,
+ NI_GPCT_FILTER_2x_TIMEBASE_3 = 0x6
+};
+
+/* PFI digital filtering options for ni m-series for use with
+ * INSN_CONFIG_FILTER. */
+enum ni_pfi_filter_select {
+ NI_PFI_FILTER_OFF = 0x0,
+ NI_PFI_FILTER_125ns = 0x1,
+ NI_PFI_FILTER_6425ns = 0x2,
+ NI_PFI_FILTER_2550us = 0x3
+};
+
+/* master clock sources for ni mio boards and INSN_CONFIG_SET_CLOCK_SRC */
+enum ni_mio_clock_source {
+ NI_MIO_INTERNAL_CLOCK = 0,
+ NI_MIO_RTSI_CLOCK = 1, /* doesn't work for m-series, use
+ NI_MIO_PLL_RTSI_CLOCK() */
+ /* the NI_MIO_PLL_* sources are m-series only */
+ NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK = 2,
+ NI_MIO_PLL_PXI10_CLOCK = 3,
+ NI_MIO_PLL_RTSI0_CLOCK = 4
+};
+
+#define NI_MIO_PLL_RTSI_CLOCK(x) (NI_MIO_PLL_RTSI0_CLOCK + (x))
+
+/* Signals which can be routed to an NI RTSI pin with INSN_CONFIG_SET_ROUTING.
+ The numbers assigned are not arbitrary, they correspond to the bits required
+ to program the board. */
+enum ni_rtsi_routing {
+ NI_RTSI_OUTPUT_ADR_START1 = 0,
+ NI_RTSI_OUTPUT_ADR_START2 = 1,
+ NI_RTSI_OUTPUT_SCLKG = 2,
+ NI_RTSI_OUTPUT_DACUPDN = 3,
+ NI_RTSI_OUTPUT_DA_START1 = 4,
+ NI_RTSI_OUTPUT_G_SRC0 = 5,
+ NI_RTSI_OUTPUT_G_GATE0 = 6,
+ NI_RTSI_OUTPUT_RGOUT0 = 7,
+ NI_RTSI_OUTPUT_RTSI_BRD_0 = 8,
+ NI_RTSI_OUTPUT_RTSI_OSC = 12 /* pre-m-series always have RTSI
+ * clock on line 7 */
+};
+
+#define NI_RTSI_OUTPUT_RTSI_BRD(x) (NI_RTSI_OUTPUT_RTSI_BRD_0 + (x))
+
+/* Signals which can be routed to an NI PFI pin on an m-series board with
+ * INSN_CONFIG_SET_ROUTING. These numbers are also returned by
+ * INSN_CONFIG_GET_ROUTING on pre-m-series boards, even though their routing
+ * cannot be changed. The numbers assigned are not arbitrary, they correspond
+ * to the bits required to program the board. */
+enum ni_pfi_routing {
+ NI_PFI_OUTPUT_PFI_DEFAULT = 0,
+ NI_PFI_OUTPUT_AI_START1 = 1,
+ NI_PFI_OUTPUT_AI_START2 = 2,
+ NI_PFI_OUTPUT_AI_CONVERT = 3,
+ NI_PFI_OUTPUT_G_SRC1 = 4,
+ NI_PFI_OUTPUT_G_GATE1 = 5,
+ NI_PFI_OUTPUT_AO_UPDATE_N = 6,
+ NI_PFI_OUTPUT_AO_START1 = 7,
+ NI_PFI_OUTPUT_AI_START_PULSE = 8,
+ NI_PFI_OUTPUT_G_SRC0 = 9,
+ NI_PFI_OUTPUT_G_GATE0 = 10,
+ NI_PFI_OUTPUT_EXT_STROBE = 11,
+ NI_PFI_OUTPUT_AI_EXT_MUX_CLK = 12,
+ NI_PFI_OUTPUT_GOUT0 = 13,
+ NI_PFI_OUTPUT_GOUT1 = 14,
+ NI_PFI_OUTPUT_FREQ_OUT = 15,
+ NI_PFI_OUTPUT_PFI_DO = 16,
+ NI_PFI_OUTPUT_I_ATRIG = 17,
+ NI_PFI_OUTPUT_RTSI0 = 18,
+ NI_PFI_OUTPUT_PXI_STAR_TRIGGER_IN = 26,
+ NI_PFI_OUTPUT_SCXI_TRIG1 = 27,
+ NI_PFI_OUTPUT_DIO_CHANGE_DETECT_RTSI = 28,
+ NI_PFI_OUTPUT_CDI_SAMPLE = 29,
+ NI_PFI_OUTPUT_CDO_UPDATE = 30
+};
+
+#define NI_PFI_OUTPUT_RTSI(x) (NI_PFI_OUTPUT_RTSI0 + (x))
+
+/* Signals which can be routed to output on a NI PFI pin on a 660x board
+ with INSN_CONFIG_SET_ROUTING. The numbers assigned are
+ not arbitrary, they correspond to the bits required
+ to program the board. Lines 0 to 7 can only be set to
+ NI_660X_PFI_OUTPUT_DIO. Lines 32 to 39 can only be set to
+ NI_660X_PFI_OUTPUT_COUNTER. */
+enum ni_660x_pfi_routing {
+ NI_660X_PFI_OUTPUT_COUNTER = 1, /* counter */
+ NI_660X_PFI_OUTPUT_DIO = 2, /* static digital output */
+};
+
+/* NI External Trigger lines. These values are not arbitrary, but are related
+ * to the bits required to program the board (offset by 1 for historical
+ * reasons). */
+#define NI_EXT_PFI(x) (NI_USUAL_PFI_SELECT(x) - 1)
+#define NI_EXT_RTSI(x) (NI_USUAL_RTSI_SELECT(x) - 1)
+
+/* status bits for INSN_CONFIG_GET_COUNTER_STATUS */
+enum comedi_counter_status_flags {
+ COMEDI_COUNTER_ARMED = 0x1,
+ COMEDI_COUNTER_COUNTING = 0x2,
+ COMEDI_COUNTER_TERMINAL_COUNT = 0x4,
+};
+
+/* Clock sources for CDIO subdevice on NI m-series boards. Used as the
+ * scan_begin_arg for a comedi_command. These sources may also be bitwise-or'd
+ * with CR_INVERT to change polarity. */
+enum ni_m_series_cdio_scan_begin_src {
+ NI_CDIO_SCAN_BEGIN_SRC_GROUND = 0,
+ NI_CDIO_SCAN_BEGIN_SRC_AI_START = 18,
+ NI_CDIO_SCAN_BEGIN_SRC_AI_CONVERT = 19,
+ NI_CDIO_SCAN_BEGIN_SRC_PXI_STAR_TRIGGER = 20,
+ NI_CDIO_SCAN_BEGIN_SRC_G0_OUT = 28,
+ NI_CDIO_SCAN_BEGIN_SRC_G1_OUT = 29,
+ NI_CDIO_SCAN_BEGIN_SRC_ANALOG_TRIGGER = 30,
+ NI_CDIO_SCAN_BEGIN_SRC_AO_UPDATE = 31,
+ NI_CDIO_SCAN_BEGIN_SRC_FREQ_OUT = 32,
+ NI_CDIO_SCAN_BEGIN_SRC_DIO_CHANGE_DETECT_IRQ = 33
+};
+
+#define NI_CDIO_SCAN_BEGIN_SRC_PFI(x) NI_USUAL_PFI_SELECT(x)
+#define NI_CDIO_SCAN_BEGIN_SRC_RTSI(x) NI_USUAL_RTSI_SELECT(x)
+
+/* scan_begin_src for scan_begin_arg==TRIG_EXT with analog output command on NI
+ * boards. These scan begin sources can also be bitwise-or'd with CR_INVERT to
+ * change polarity. */
+#define NI_AO_SCAN_BEGIN_SRC_PFI(x) NI_USUAL_PFI_SELECT(x)
+#define NI_AO_SCAN_BEGIN_SRC_RTSI(x) NI_USUAL_RTSI_SELECT(x)
+
+/* Bits for setting a clock source with
+ * INSN_CONFIG_SET_CLOCK_SRC when using NI frequency output subdevice. */
+enum ni_freq_out_clock_source_bits {
+ NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC, /* 10 MHz */
+ NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC /* 100 KHz */
+};
+
+/* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
+ * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */
+enum amplc_dio_clock_source {
+ AMPLC_DIO_CLK_CLKN, /* per channel external clock
+ input/output pin (pin is only an
+ input when clock source set to this
+ value, otherwise it is an output) */
+ AMPLC_DIO_CLK_10MHZ, /* 10 MHz internal clock */
+ AMPLC_DIO_CLK_1MHZ, /* 1 MHz internal clock */
+ AMPLC_DIO_CLK_100KHZ, /* 100 kHz internal clock */
+ AMPLC_DIO_CLK_10KHZ, /* 10 kHz internal clock */
+ AMPLC_DIO_CLK_1KHZ, /* 1 kHz internal clock */
+ AMPLC_DIO_CLK_OUTNM1, /* output of preceding counter channel
+ (for channel 0, preceding counter
+ channel is channel 2 on preceding
+ counter subdevice, for first counter
+ subdevice, preceding counter
+ subdevice is the last counter
+ subdevice) */
+ AMPLC_DIO_CLK_EXT, /* per chip external input pin */
+ /* the following are "enhanced" clock sources for PCIe models */
+ AMPLC_DIO_CLK_VCC, /* clock input HIGH */
+ AMPLC_DIO_CLK_GND, /* clock input LOW */
+ AMPLC_DIO_CLK_PAT_PRESENT, /* "pattern present" signal */
+ AMPLC_DIO_CLK_20MHZ /* 20 MHz internal clock */
+};
+
+/* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
+ * timer subdevice on some Amplicon DIO PCIe boards (amplc_dio200 driver). */
+enum amplc_dio_ts_clock_src {
+ AMPLC_DIO_TS_CLK_1GHZ, /* 1 ns period with 20 ns granularity */
+ AMPLC_DIO_TS_CLK_1MHZ, /* 1 us period */
+ AMPLC_DIO_TS_CLK_1KHZ /* 1 ms period */
+};
+
+/* Values for setting a gate source with INSN_CONFIG_SET_GATE_SRC for
+ * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */
+enum amplc_dio_gate_source {
+ AMPLC_DIO_GAT_VCC, /* internal high logic level */
+ AMPLC_DIO_GAT_GND, /* internal low logic level */
+ AMPLC_DIO_GAT_GATN, /* per channel external gate input */
+ AMPLC_DIO_GAT_NOUTNM2, /* negated output of counter channel
+ minus 2 (for channels 0 or 1,
+ channel minus 2 is channel 1 or 2 on
+ the preceding counter subdevice, for
+ the first counter subdevice the
+ preceding counter subdevice is the
+ last counter subdevice) */
+ AMPLC_DIO_GAT_RESERVED4,
+ AMPLC_DIO_GAT_RESERVED5,
+ AMPLC_DIO_GAT_RESERVED6,
+ AMPLC_DIO_GAT_RESERVED7,
+ /* the following are "enhanced" gate sources for PCIe models */
+ AMPLC_DIO_GAT_NGATN = 6, /* negated per channel gate input */
+ AMPLC_DIO_GAT_OUTNM2, /* non-negated output of counter
+ channel minus 2 */
+ AMPLC_DIO_GAT_PAT_PRESENT, /* "pattern present" signal */
+ AMPLC_DIO_GAT_PAT_OCCURRED, /* "pattern occurred" latched */
+ AMPLC_DIO_GAT_PAT_GONE, /* "pattern gone away" latched */
+ AMPLC_DIO_GAT_NPAT_PRESENT, /* negated "pattern present" */
+ AMPLC_DIO_GAT_NPAT_OCCURRED, /* negated "pattern occurred" */
+ AMPLC_DIO_GAT_NPAT_GONE /* negated "pattern gone away" */
+};
+
+/*
+ * Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
+ * the counter subdevice on the Kolter Electronic PCI-Counter board
+ * (ke_counter driver).
+ */
+enum ke_counter_clock_source {
+ KE_CLK_20MHZ, /* internal 20MHz (default) */
+ KE_CLK_4MHZ, /* internal 4MHz (option) */
+ KE_CLK_EXT /* external clock on pin 21 of D-Sub */
+};
+
+#endif /* _COMEDI_H */
diff --git a/drivers/staging/comedi/comedi_buf.c b/drivers/staging/comedi/comedi_buf.c
new file mode 100644
index 000000000..19e7b229d
--- /dev/null
+++ b/drivers/staging/comedi/comedi_buf.c
@@ -0,0 +1,550 @@
+/*
+ * comedi_buf.c
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+
+#include "comedidev.h"
+#include "comedi_internal.h"
+
+#ifdef PAGE_KERNEL_NOCACHE
+#define COMEDI_PAGE_PROTECTION PAGE_KERNEL_NOCACHE
+#else
+#define COMEDI_PAGE_PROTECTION PAGE_KERNEL
+#endif
+
+static void comedi_buf_map_kref_release(struct kref *kref)
+{
+ struct comedi_buf_map *bm =
+ container_of(kref, struct comedi_buf_map, refcount);
+ struct comedi_buf_page *buf;
+ unsigned int i;
+
+ if (bm->page_list) {
+ for (i = 0; i < bm->n_pages; i++) {
+ buf = &bm->page_list[i];
+ clear_bit(PG_reserved,
+ &(virt_to_page(buf->virt_addr)->flags));
+ if (bm->dma_dir != DMA_NONE) {
+#ifdef CONFIG_HAS_DMA
+ dma_free_coherent(bm->dma_hw_dev,
+ PAGE_SIZE,
+ buf->virt_addr,
+ buf->dma_addr);
+#endif
+ } else {
+ free_page((unsigned long)buf->virt_addr);
+ }
+ }
+ vfree(bm->page_list);
+ }
+ if (bm->dma_dir != DMA_NONE)
+ put_device(bm->dma_hw_dev);
+ kfree(bm);
+}
+
+static void __comedi_buf_free(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_buf_map *bm;
+ unsigned long flags;
+
+ if (async->prealloc_buf) {
+ vunmap(async->prealloc_buf);
+ async->prealloc_buf = NULL;
+ async->prealloc_bufsz = 0;
+ }
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+ bm = async->buf_map;
+ async->buf_map = NULL;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+ comedi_buf_map_put(bm);
+}
+
+static void __comedi_buf_alloc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned n_pages)
+{
+ struct comedi_async *async = s->async;
+ struct page **pages = NULL;
+ struct comedi_buf_map *bm;
+ struct comedi_buf_page *buf;
+ unsigned long flags;
+ unsigned i;
+
+ if (!IS_ENABLED(CONFIG_HAS_DMA) && s->async_dma_dir != DMA_NONE) {
+ dev_err(dev->class_dev,
+ "dma buffer allocation not supported\n");
+ return;
+ }
+
+ bm = kzalloc(sizeof(*async->buf_map), GFP_KERNEL);
+ if (!bm)
+ return;
+
+ kref_init(&bm->refcount);
+ spin_lock_irqsave(&s->spin_lock, flags);
+ async->buf_map = bm;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+ bm->dma_dir = s->async_dma_dir;
+ if (bm->dma_dir != DMA_NONE)
+ /* Need ref to hardware device to free buffer later. */
+ bm->dma_hw_dev = get_device(dev->hw_dev);
+
+ bm->page_list = vzalloc(sizeof(*buf) * n_pages);
+ if (bm->page_list)
+ pages = vmalloc(sizeof(struct page *) * n_pages);
+
+ if (!pages)
+ return;
+
+ for (i = 0; i < n_pages; i++) {
+ buf = &bm->page_list[i];
+ if (bm->dma_dir != DMA_NONE)
+#ifdef CONFIG_HAS_DMA
+ buf->virt_addr = dma_alloc_coherent(bm->dma_hw_dev,
+ PAGE_SIZE,
+ &buf->dma_addr,
+ GFP_KERNEL |
+ __GFP_COMP);
+#else
+ break;
+#endif
+ else
+ buf->virt_addr = (void *)get_zeroed_page(GFP_KERNEL);
+ if (!buf->virt_addr)
+ break;
+
+ set_bit(PG_reserved, &(virt_to_page(buf->virt_addr)->flags));
+
+ pages[i] = virt_to_page(buf->virt_addr);
+ }
+ spin_lock_irqsave(&s->spin_lock, flags);
+ bm->n_pages = i;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+
+ /* vmap the prealloc_buf if all the pages were allocated */
+ if (i == n_pages)
+ async->prealloc_buf = vmap(pages, n_pages, VM_MAP,
+ COMEDI_PAGE_PROTECTION);
+
+ vfree(pages);
+}
+
+void comedi_buf_map_get(struct comedi_buf_map *bm)
+{
+ if (bm)
+ kref_get(&bm->refcount);
+}
+
+int comedi_buf_map_put(struct comedi_buf_map *bm)
+{
+ if (bm)
+ return kref_put(&bm->refcount, comedi_buf_map_kref_release);
+ return 1;
+}
+
+/* returns s->async->buf_map and increments its kref refcount */
+struct comedi_buf_map *
+comedi_buf_map_from_subdev_get(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_buf_map *bm = NULL;
+ unsigned long flags;
+
+ if (!async)
+ return NULL;
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+ bm = async->buf_map;
+ /* only want it if buffer pages allocated */
+ if (bm && bm->n_pages)
+ comedi_buf_map_get(bm);
+ else
+ bm = NULL;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+
+ return bm;
+}
+
+bool comedi_buf_is_mmapped(struct comedi_subdevice *s)
+{
+ struct comedi_buf_map *bm = s->async->buf_map;
+
+ return bm && (atomic_read(&bm->refcount.refcount) > 1);
+}
+
+int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s,
+ unsigned long new_size)
+{
+ struct comedi_async *async = s->async;
+
+ /* Round up new_size to multiple of PAGE_SIZE */
+ new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK;
+
+ /* if no change is required, do nothing */
+ if (async->prealloc_buf && async->prealloc_bufsz == new_size)
+ return 0;
+
+ /* deallocate old buffer */
+ __comedi_buf_free(dev, s);
+
+ /* allocate new buffer */
+ if (new_size) {
+ unsigned n_pages = new_size >> PAGE_SHIFT;
+
+ __comedi_buf_alloc(dev, s, n_pages);
+
+ if (!async->prealloc_buf) {
+ /* allocation failed */
+ __comedi_buf_free(dev, s);
+ return -ENOMEM;
+ }
+ }
+ async->prealloc_bufsz = new_size;
+
+ return 0;
+}
+
+void comedi_buf_reset(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+
+ async->buf_write_alloc_count = 0;
+ async->buf_write_count = 0;
+ async->buf_read_alloc_count = 0;
+ async->buf_read_count = 0;
+
+ async->buf_write_ptr = 0;
+ async->buf_read_ptr = 0;
+
+ async->cur_chan = 0;
+ async->scans_done = 0;
+ async->scan_progress = 0;
+ async->munge_chan = 0;
+ async->munge_count = 0;
+ async->munge_ptr = 0;
+
+ async->events = 0;
+}
+
+static unsigned int comedi_buf_write_n_available(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ unsigned int free_end = async->buf_read_count + async->prealloc_bufsz;
+
+ return free_end - async->buf_write_alloc_count;
+}
+
+/* allocates chunk for the writer from free buffer space */
+unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int available = comedi_buf_write_n_available(s);
+
+ if (nbytes > available)
+ nbytes = available;
+
+ async->buf_write_alloc_count += nbytes;
+
+ /*
+ * ensure the async buffer 'counts' are read and updated
+ * before we write data to the write-alloc'ed buffer space
+ */
+ smp_mb();
+
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_write_alloc);
+
+/*
+ * munging is applied to data by core as it passes between user
+ * and kernel space
+ */
+static unsigned int comedi_buf_munge(struct comedi_subdevice *s,
+ unsigned int num_bytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int count = 0;
+ const unsigned num_sample_bytes = comedi_bytes_per_sample(s);
+
+ if (!s->munge || (async->cmd.flags & CMDF_RAWDATA)) {
+ async->munge_count += num_bytes;
+ count = num_bytes;
+ } else {
+ /* don't munge partial samples */
+ num_bytes -= num_bytes % num_sample_bytes;
+ while (count < num_bytes) {
+ int block_size = num_bytes - count;
+ unsigned int buf_end;
+
+ buf_end = async->prealloc_bufsz - async->munge_ptr;
+ if (block_size > buf_end)
+ block_size = buf_end;
+
+ s->munge(s->device, s,
+ async->prealloc_buf + async->munge_ptr,
+ block_size, async->munge_chan);
+
+ /*
+ * ensure data is munged in buffer before the
+ * async buffer munge_count is incremented
+ */
+ smp_wmb();
+
+ async->munge_chan += block_size / num_sample_bytes;
+ async->munge_chan %= async->cmd.chanlist_len;
+ async->munge_count += block_size;
+ async->munge_ptr += block_size;
+ async->munge_ptr %= async->prealloc_bufsz;
+ count += block_size;
+ }
+ }
+
+ return count;
+}
+
+unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+
+ return async->buf_write_alloc_count - async->buf_write_count;
+}
+
+/* transfers a chunk from writer to filled buffer space */
+unsigned int comedi_buf_write_free(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int allocated = comedi_buf_write_n_allocated(s);
+
+ if (nbytes > allocated)
+ nbytes = allocated;
+
+ async->buf_write_count += nbytes;
+ async->buf_write_ptr += nbytes;
+ comedi_buf_munge(s, async->buf_write_count - async->munge_count);
+ if (async->buf_write_ptr >= async->prealloc_bufsz)
+ async->buf_write_ptr %= async->prealloc_bufsz;
+
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_write_free);
+
+unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ unsigned num_bytes;
+
+ if (!async)
+ return 0;
+
+ num_bytes = async->munge_count - async->buf_read_count;
+
+ /*
+ * ensure the async buffer 'counts' are read before we
+ * attempt to read data from the buffer
+ */
+ smp_rmb();
+
+ return num_bytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_read_n_available);
+
+/* allocates a chunk for the reader from filled (and munged) buffer space */
+unsigned int comedi_buf_read_alloc(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int available;
+
+ available = async->munge_count - async->buf_read_alloc_count;
+ if (nbytes > available)
+ nbytes = available;
+
+ async->buf_read_alloc_count += nbytes;
+
+ /*
+ * ensure the async buffer 'counts' are read before we
+ * attempt to read data from the read-alloc'ed buffer space
+ */
+ smp_rmb();
+
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_read_alloc);
+
+static unsigned int comedi_buf_read_n_allocated(struct comedi_async *async)
+{
+ return async->buf_read_alloc_count - async->buf_read_count;
+}
+
+/* transfers control of a chunk from reader to free buffer space */
+unsigned int comedi_buf_read_free(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int allocated;
+
+ /*
+ * ensure data has been read out of buffer before
+ * the async read count is incremented
+ */
+ smp_mb();
+
+ allocated = comedi_buf_read_n_allocated(async);
+ if (nbytes > allocated)
+ nbytes = allocated;
+
+ async->buf_read_count += nbytes;
+ async->buf_read_ptr += nbytes;
+ async->buf_read_ptr %= async->prealloc_bufsz;
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_read_free);
+
+static void comedi_buf_memcpy_to(struct comedi_subdevice *s,
+ const void *data, unsigned int num_bytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int write_ptr = async->buf_write_ptr;
+
+ while (num_bytes) {
+ unsigned int block_size;
+
+ if (write_ptr + num_bytes > async->prealloc_bufsz)
+ block_size = async->prealloc_bufsz - write_ptr;
+ else
+ block_size = num_bytes;
+
+ memcpy(async->prealloc_buf + write_ptr, data, block_size);
+
+ data += block_size;
+ num_bytes -= block_size;
+
+ write_ptr = 0;
+ }
+}
+
+static void comedi_buf_memcpy_from(struct comedi_subdevice *s,
+ void *dest, unsigned int nbytes)
+{
+ void *src;
+ struct comedi_async *async = s->async;
+ unsigned int read_ptr = async->buf_read_ptr;
+
+ while (nbytes) {
+ unsigned int block_size;
+
+ src = async->prealloc_buf + read_ptr;
+
+ if (nbytes >= async->prealloc_bufsz - read_ptr)
+ block_size = async->prealloc_bufsz - read_ptr;
+ else
+ block_size = nbytes;
+
+ memcpy(dest, src, block_size);
+ nbytes -= block_size;
+ dest += block_size;
+ read_ptr = 0;
+ }
+}
+
+/**
+ * comedi_buf_write_samples - write sample data to comedi buffer
+ * @s: comedi_subdevice struct
+ * @data: samples
+ * @nsamples: number of samples
+ *
+ * Writes nsamples to the comedi buffer associated with the subdevice, marks
+ * it as written and updates the acquisition scan progress.
+ *
+ * Returns the amount of data written in bytes.
+ */
+unsigned int comedi_buf_write_samples(struct comedi_subdevice *s,
+ const void *data, unsigned int nsamples)
+{
+ unsigned int max_samples;
+ unsigned int nbytes;
+
+ /*
+ * Make sure there is enough room in the buffer for all the samples.
+ * If not, clamp the nsamples to the number that will fit, flag the
+ * buffer overrun and add the samples that fit.
+ */
+ max_samples = comedi_bytes_to_samples(s,
+ comedi_buf_write_n_available(s));
+ if (nsamples > max_samples) {
+ dev_warn(s->device->class_dev, "buffer overrun\n");
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ nsamples = max_samples;
+ }
+
+ if (nsamples == 0)
+ return 0;
+
+ nbytes = comedi_buf_write_alloc(s,
+ comedi_samples_to_bytes(s, nsamples));
+ comedi_buf_memcpy_to(s, data, nbytes);
+ comedi_buf_write_free(s, nbytes);
+ comedi_inc_scan_progress(s, nbytes);
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_write_samples);
+
+/**
+ * comedi_buf_read_samples - read sample data from comedi buffer
+ * @s: comedi_subdevice struct
+ * @data: destination
+ * @nsamples: maximum number of samples to read
+ *
+ * Reads up to nsamples from the comedi buffer associated with the subdevice,
+ * marks it as read and updates the acquisition scan progress.
+ *
+ * Returns the amount of data read in bytes.
+ */
+unsigned int comedi_buf_read_samples(struct comedi_subdevice *s,
+ void *data, unsigned int nsamples)
+{
+ unsigned int max_samples;
+ unsigned int nbytes;
+
+ /* clamp nsamples to the number of full samples available */
+ max_samples = comedi_bytes_to_samples(s,
+ comedi_buf_read_n_available(s));
+ if (nsamples > max_samples)
+ nsamples = max_samples;
+
+ if (nsamples == 0)
+ return 0;
+
+ nbytes = comedi_buf_read_alloc(s,
+ comedi_samples_to_bytes(s, nsamples));
+ comedi_buf_memcpy_from(s, data, nbytes);
+ comedi_buf_read_free(s, nbytes);
+ comedi_inc_scan_progress(s, nbytes);
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_read_samples);
diff --git a/drivers/staging/comedi/comedi_compat32.c b/drivers/staging/comedi/comedi_compat32.c
new file mode 100644
index 000000000..25848244c
--- /dev/null
+++ b/drivers/staging/comedi/comedi_compat32.c
@@ -0,0 +1,463 @@
+/*
+ * comedi/comedi_compat32.c
+ * 32-bit ioctl compatibility for 64-bit comedi kernel module.
+ *
+ * Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
+ * Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/uaccess.h>
+#include <linux/compat.h>
+#include <linux/fs.h>
+#include "comedi.h"
+#include "comedi_compat32.h"
+
+#define COMEDI32_CHANINFO _IOR(CIO, 3, struct comedi32_chaninfo_struct)
+#define COMEDI32_RANGEINFO _IOR(CIO, 8, struct comedi32_rangeinfo_struct)
+/*
+ * N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR.
+ * It's too late to change it now, but it only affects the command number.
+ */
+#define COMEDI32_CMD _IOR(CIO, 9, struct comedi32_cmd_struct)
+/*
+ * N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR.
+ * It's too late to change it now, but it only affects the command number.
+ */
+#define COMEDI32_CMDTEST _IOR(CIO, 10, struct comedi32_cmd_struct)
+#define COMEDI32_INSNLIST _IOR(CIO, 11, struct comedi32_insnlist_struct)
+#define COMEDI32_INSN _IOR(CIO, 12, struct comedi32_insn_struct)
+
+struct comedi32_chaninfo_struct {
+ unsigned int subdev;
+ compat_uptr_t maxdata_list; /* 32-bit 'unsigned int *' */
+ compat_uptr_t flaglist; /* 32-bit 'unsigned int *' */
+ compat_uptr_t rangelist; /* 32-bit 'unsigned int *' */
+ unsigned int unused[4];
+};
+
+struct comedi32_rangeinfo_struct {
+ unsigned int range_type;
+ compat_uptr_t range_ptr; /* 32-bit 'void *' */
+};
+
+struct comedi32_cmd_struct {
+ unsigned int subdev;
+ unsigned int flags;
+ unsigned int start_src;
+ unsigned int start_arg;
+ unsigned int scan_begin_src;
+ unsigned int scan_begin_arg;
+ unsigned int convert_src;
+ unsigned int convert_arg;
+ unsigned int scan_end_src;
+ unsigned int scan_end_arg;
+ unsigned int stop_src;
+ unsigned int stop_arg;
+ compat_uptr_t chanlist; /* 32-bit 'unsigned int *' */
+ unsigned int chanlist_len;
+ compat_uptr_t data; /* 32-bit 'short *' */
+ unsigned int data_len;
+};
+
+struct comedi32_insn_struct {
+ unsigned int insn;
+ unsigned int n;
+ compat_uptr_t data; /* 32-bit 'unsigned int *' */
+ unsigned int subdev;
+ unsigned int chanspec;
+ unsigned int unused[3];
+};
+
+struct comedi32_insnlist_struct {
+ unsigned int n_insns;
+ compat_uptr_t insns; /* 32-bit 'struct comedi_insn *' */
+};
+
+/* Handle translated ioctl. */
+static int translated_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ if (file->f_op->unlocked_ioctl)
+ return file->f_op->unlocked_ioctl(file, cmd, arg);
+
+ return -ENOTTY;
+}
+
+/* Handle 32-bit COMEDI_CHANINFO ioctl. */
+static int compat_chaninfo(struct file *file, unsigned long arg)
+{
+ struct comedi_chaninfo __user *chaninfo;
+ struct comedi32_chaninfo_struct __user *chaninfo32;
+ int err;
+ union {
+ unsigned int uint;
+ compat_uptr_t uptr;
+ } temp;
+
+ chaninfo32 = compat_ptr(arg);
+ chaninfo = compat_alloc_user_space(sizeof(*chaninfo));
+
+ /* Copy chaninfo structure. Ignore unused members. */
+ if (!access_ok(VERIFY_READ, chaninfo32, sizeof(*chaninfo32)) ||
+ !access_ok(VERIFY_WRITE, chaninfo, sizeof(*chaninfo)))
+ return -EFAULT;
+
+ err = 0;
+ err |= __get_user(temp.uint, &chaninfo32->subdev);
+ err |= __put_user(temp.uint, &chaninfo->subdev);
+ err |= __get_user(temp.uptr, &chaninfo32->maxdata_list);
+ err |= __put_user(compat_ptr(temp.uptr), &chaninfo->maxdata_list);
+ err |= __get_user(temp.uptr, &chaninfo32->flaglist);
+ err |= __put_user(compat_ptr(temp.uptr), &chaninfo->flaglist);
+ err |= __get_user(temp.uptr, &chaninfo32->rangelist);
+ err |= __put_user(compat_ptr(temp.uptr), &chaninfo->rangelist);
+ if (err)
+ return -EFAULT;
+
+ return translated_ioctl(file, COMEDI_CHANINFO, (unsigned long)chaninfo);
+}
+
+/* Handle 32-bit COMEDI_RANGEINFO ioctl. */
+static int compat_rangeinfo(struct file *file, unsigned long arg)
+{
+ struct comedi_rangeinfo __user *rangeinfo;
+ struct comedi32_rangeinfo_struct __user *rangeinfo32;
+ int err;
+ union {
+ unsigned int uint;
+ compat_uptr_t uptr;
+ } temp;
+
+ rangeinfo32 = compat_ptr(arg);
+ rangeinfo = compat_alloc_user_space(sizeof(*rangeinfo));
+
+ /* Copy rangeinfo structure. */
+ if (!access_ok(VERIFY_READ, rangeinfo32, sizeof(*rangeinfo32)) ||
+ !access_ok(VERIFY_WRITE, rangeinfo, sizeof(*rangeinfo)))
+ return -EFAULT;
+
+ err = 0;
+ err |= __get_user(temp.uint, &rangeinfo32->range_type);
+ err |= __put_user(temp.uint, &rangeinfo->range_type);
+ err |= __get_user(temp.uptr, &rangeinfo32->range_ptr);
+ err |= __put_user(compat_ptr(temp.uptr), &rangeinfo->range_ptr);
+ if (err)
+ return -EFAULT;
+
+ return translated_ioctl(file, COMEDI_RANGEINFO,
+ (unsigned long)rangeinfo);
+}
+
+/* Copy 32-bit cmd structure to native cmd structure. */
+static int get_compat_cmd(struct comedi_cmd __user *cmd,
+ struct comedi32_cmd_struct __user *cmd32)
+{
+ int err;
+ union {
+ unsigned int uint;
+ compat_uptr_t uptr;
+ } temp;
+
+ /* Copy cmd structure. */
+ if (!access_ok(VERIFY_READ, cmd32, sizeof(*cmd32)) ||
+ !access_ok(VERIFY_WRITE, cmd, sizeof(*cmd)))
+ return -EFAULT;
+
+ err = 0;
+ err |= __get_user(temp.uint, &cmd32->subdev);
+ err |= __put_user(temp.uint, &cmd->subdev);
+ err |= __get_user(temp.uint, &cmd32->flags);
+ err |= __put_user(temp.uint, &cmd->flags);
+ err |= __get_user(temp.uint, &cmd32->start_src);
+ err |= __put_user(temp.uint, &cmd->start_src);
+ err |= __get_user(temp.uint, &cmd32->start_arg);
+ err |= __put_user(temp.uint, &cmd->start_arg);
+ err |= __get_user(temp.uint, &cmd32->scan_begin_src);
+ err |= __put_user(temp.uint, &cmd->scan_begin_src);
+ err |= __get_user(temp.uint, &cmd32->scan_begin_arg);
+ err |= __put_user(temp.uint, &cmd->scan_begin_arg);
+ err |= __get_user(temp.uint, &cmd32->convert_src);
+ err |= __put_user(temp.uint, &cmd->convert_src);
+ err |= __get_user(temp.uint, &cmd32->convert_arg);
+ err |= __put_user(temp.uint, &cmd->convert_arg);
+ err |= __get_user(temp.uint, &cmd32->scan_end_src);
+ err |= __put_user(temp.uint, &cmd->scan_end_src);
+ err |= __get_user(temp.uint, &cmd32->scan_end_arg);
+ err |= __put_user(temp.uint, &cmd->scan_end_arg);
+ err |= __get_user(temp.uint, &cmd32->stop_src);
+ err |= __put_user(temp.uint, &cmd->stop_src);
+ err |= __get_user(temp.uint, &cmd32->stop_arg);
+ err |= __put_user(temp.uint, &cmd->stop_arg);
+ err |= __get_user(temp.uptr, &cmd32->chanlist);
+ err |= __put_user(compat_ptr(temp.uptr), &cmd->chanlist);
+ err |= __get_user(temp.uint, &cmd32->chanlist_len);
+ err |= __put_user(temp.uint, &cmd->chanlist_len);
+ err |= __get_user(temp.uptr, &cmd32->data);
+ err |= __put_user(compat_ptr(temp.uptr), &cmd->data);
+ err |= __get_user(temp.uint, &cmd32->data_len);
+ err |= __put_user(temp.uint, &cmd->data_len);
+ return err ? -EFAULT : 0;
+}
+
+/* Copy native cmd structure to 32-bit cmd structure. */
+static int put_compat_cmd(struct comedi32_cmd_struct __user *cmd32,
+ struct comedi_cmd __user *cmd)
+{
+ int err;
+ unsigned int temp;
+
+ /*
+ * Copy back most of cmd structure.
+ *
+ * Assume the pointer values are already valid.
+ * (Could use ptr_to_compat() to set them.)
+ */
+ if (!access_ok(VERIFY_READ, cmd, sizeof(*cmd)) ||
+ !access_ok(VERIFY_WRITE, cmd32, sizeof(*cmd32)))
+ return -EFAULT;
+
+ err = 0;
+ err |= __get_user(temp, &cmd->subdev);
+ err |= __put_user(temp, &cmd32->subdev);
+ err |= __get_user(temp, &cmd->flags);
+ err |= __put_user(temp, &cmd32->flags);
+ err |= __get_user(temp, &cmd->start_src);
+ err |= __put_user(temp, &cmd32->start_src);
+ err |= __get_user(temp, &cmd->start_arg);
+ err |= __put_user(temp, &cmd32->start_arg);
+ err |= __get_user(temp, &cmd->scan_begin_src);
+ err |= __put_user(temp, &cmd32->scan_begin_src);
+ err |= __get_user(temp, &cmd->scan_begin_arg);
+ err |= __put_user(temp, &cmd32->scan_begin_arg);
+ err |= __get_user(temp, &cmd->convert_src);
+ err |= __put_user(temp, &cmd32->convert_src);
+ err |= __get_user(temp, &cmd->convert_arg);
+ err |= __put_user(temp, &cmd32->convert_arg);
+ err |= __get_user(temp, &cmd->scan_end_src);
+ err |= __put_user(temp, &cmd32->scan_end_src);
+ err |= __get_user(temp, &cmd->scan_end_arg);
+ err |= __put_user(temp, &cmd32->scan_end_arg);
+ err |= __get_user(temp, &cmd->stop_src);
+ err |= __put_user(temp, &cmd32->stop_src);
+ err |= __get_user(temp, &cmd->stop_arg);
+ err |= __put_user(temp, &cmd32->stop_arg);
+ /* Assume chanlist pointer is unchanged. */
+ err |= __get_user(temp, &cmd->chanlist_len);
+ err |= __put_user(temp, &cmd32->chanlist_len);
+ /* Assume data pointer is unchanged. */
+ err |= __get_user(temp, &cmd->data_len);
+ err |= __put_user(temp, &cmd32->data_len);
+ return err ? -EFAULT : 0;
+}
+
+/* Handle 32-bit COMEDI_CMD ioctl. */
+static int compat_cmd(struct file *file, unsigned long arg)
+{
+ struct comedi_cmd __user *cmd;
+ struct comedi32_cmd_struct __user *cmd32;
+ int rc, err;
+
+ cmd32 = compat_ptr(arg);
+ cmd = compat_alloc_user_space(sizeof(*cmd));
+
+ rc = get_compat_cmd(cmd, cmd32);
+ if (rc)
+ return rc;
+
+ rc = translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd);
+ if (rc == -EAGAIN) {
+ /* Special case: copy cmd back to user. */
+ err = put_compat_cmd(cmd32, cmd);
+ if (err)
+ rc = err;
+ }
+
+ return rc;
+}
+
+/* Handle 32-bit COMEDI_CMDTEST ioctl. */
+static int compat_cmdtest(struct file *file, unsigned long arg)
+{
+ struct comedi_cmd __user *cmd;
+ struct comedi32_cmd_struct __user *cmd32;
+ int rc, err;
+
+ cmd32 = compat_ptr(arg);
+ cmd = compat_alloc_user_space(sizeof(*cmd));
+
+ rc = get_compat_cmd(cmd, cmd32);
+ if (rc)
+ return rc;
+
+ rc = translated_ioctl(file, COMEDI_CMDTEST, (unsigned long)cmd);
+ if (rc < 0)
+ return rc;
+
+ err = put_compat_cmd(cmd32, cmd);
+ if (err)
+ rc = err;
+
+ return rc;
+}
+
+/* Copy 32-bit insn structure to native insn structure. */
+static int get_compat_insn(struct comedi_insn __user *insn,
+ struct comedi32_insn_struct __user *insn32)
+{
+ int err;
+ union {
+ unsigned int uint;
+ compat_uptr_t uptr;
+ } temp;
+
+ /* Copy insn structure. Ignore the unused members. */
+ err = 0;
+ if (!access_ok(VERIFY_READ, insn32, sizeof(*insn32)) ||
+ !access_ok(VERIFY_WRITE, insn, sizeof(*insn)))
+ return -EFAULT;
+
+ err |= __get_user(temp.uint, &insn32->insn);
+ err |= __put_user(temp.uint, &insn->insn);
+ err |= __get_user(temp.uint, &insn32->n);
+ err |= __put_user(temp.uint, &insn->n);
+ err |= __get_user(temp.uptr, &insn32->data);
+ err |= __put_user(compat_ptr(temp.uptr), &insn->data);
+ err |= __get_user(temp.uint, &insn32->subdev);
+ err |= __put_user(temp.uint, &insn->subdev);
+ err |= __get_user(temp.uint, &insn32->chanspec);
+ err |= __put_user(temp.uint, &insn->chanspec);
+ return err ? -EFAULT : 0;
+}
+
+/* Handle 32-bit COMEDI_INSNLIST ioctl. */
+static int compat_insnlist(struct file *file, unsigned long arg)
+{
+ struct combined_insnlist {
+ struct comedi_insnlist insnlist;
+ struct comedi_insn insn[1];
+ } __user *s;
+ struct comedi32_insnlist_struct __user *insnlist32;
+ struct comedi32_insn_struct __user *insn32;
+ compat_uptr_t uptr;
+ unsigned int n_insns, n;
+ int err, rc;
+
+ insnlist32 = compat_ptr(arg);
+
+ /* Get 32-bit insnlist structure. */
+ if (!access_ok(VERIFY_READ, insnlist32, sizeof(*insnlist32)))
+ return -EFAULT;
+
+ err = 0;
+ err |= __get_user(n_insns, &insnlist32->n_insns);
+ err |= __get_user(uptr, &insnlist32->insns);
+ insn32 = compat_ptr(uptr);
+ if (err)
+ return -EFAULT;
+
+ /* Allocate user memory to copy insnlist and insns into. */
+ s = compat_alloc_user_space(offsetof(struct combined_insnlist,
+ insn[n_insns]));
+
+ /* Set native insnlist structure. */
+ if (!access_ok(VERIFY_WRITE, &s->insnlist, sizeof(s->insnlist)))
+ return -EFAULT;
+
+ err |= __put_user(n_insns, &s->insnlist.n_insns);
+ err |= __put_user(&s->insn[0], &s->insnlist.insns);
+ if (err)
+ return -EFAULT;
+
+ /* Copy insn structures. */
+ for (n = 0; n < n_insns; n++) {
+ rc = get_compat_insn(&s->insn[n], &insn32[n]);
+ if (rc)
+ return rc;
+ }
+
+ return translated_ioctl(file, COMEDI_INSNLIST,
+ (unsigned long)&s->insnlist);
+}
+
+/* Handle 32-bit COMEDI_INSN ioctl. */
+static int compat_insn(struct file *file, unsigned long arg)
+{
+ struct comedi_insn __user *insn;
+ struct comedi32_insn_struct __user *insn32;
+ int rc;
+
+ insn32 = compat_ptr(arg);
+ insn = compat_alloc_user_space(sizeof(*insn));
+
+ rc = get_compat_insn(insn, insn32);
+ if (rc)
+ return rc;
+
+ return translated_ioctl(file, COMEDI_INSN, (unsigned long)insn);
+}
+
+/*
+ * compat_ioctl file operation.
+ *
+ * Returns -ENOIOCTLCMD for unrecognised ioctl codes.
+ */
+long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int rc;
+
+ switch (cmd) {
+ case COMEDI_DEVCONFIG:
+ case COMEDI_DEVINFO:
+ case COMEDI_SUBDINFO:
+ case COMEDI_BUFCONFIG:
+ case COMEDI_BUFINFO:
+ /* Just need to translate the pointer argument. */
+ arg = (unsigned long)compat_ptr(arg);
+ rc = translated_ioctl(file, cmd, arg);
+ break;
+ case COMEDI_LOCK:
+ case COMEDI_UNLOCK:
+ case COMEDI_CANCEL:
+ case COMEDI_POLL:
+ case COMEDI_SETRSUBD:
+ case COMEDI_SETWSUBD:
+ /* No translation needed. */
+ rc = translated_ioctl(file, cmd, arg);
+ break;
+ case COMEDI32_CHANINFO:
+ rc = compat_chaninfo(file, arg);
+ break;
+ case COMEDI32_RANGEINFO:
+ rc = compat_rangeinfo(file, arg);
+ break;
+ case COMEDI32_CMD:
+ rc = compat_cmd(file, arg);
+ break;
+ case COMEDI32_CMDTEST:
+ rc = compat_cmdtest(file, arg);
+ break;
+ case COMEDI32_INSNLIST:
+ rc = compat_insnlist(file, arg);
+ break;
+ case COMEDI32_INSN:
+ rc = compat_insn(file, arg);
+ break;
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+ return rc;
+}
diff --git a/drivers/staging/comedi/comedi_compat32.h b/drivers/staging/comedi/comedi_compat32.h
new file mode 100644
index 000000000..5ce77f3e8
--- /dev/null
+++ b/drivers/staging/comedi/comedi_compat32.h
@@ -0,0 +1,36 @@
+/*
+ * comedi/comedi_compat32.h
+ * 32-bit ioctl compatibility for 64-bit comedi kernel module.
+ *
+ * Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
+ * Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _COMEDI_COMPAT32_H
+#define _COMEDI_COMPAT32_H
+
+#ifdef CONFIG_COMPAT
+
+struct file;
+long comedi_compat_ioctl(struct file *, unsigned int cmd, unsigned long arg);
+
+#else /* CONFIG_COMPAT */
+
+#define comedi_compat_ioctl NULL
+
+#endif /* CONFIG_COMPAT */
+
+#endif /* _COMEDI_COMPAT32_H */
diff --git a/drivers/staging/comedi/comedi_fops.c b/drivers/staging/comedi/comedi_fops.c
new file mode 100644
index 000000000..e78ddbe5a
--- /dev/null
+++ b/drivers/staging/comedi/comedi_fops.c
@@ -0,0 +1,2921 @@
+/*
+ * comedi/comedi_fops.c
+ * comedi kernel module
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "comedi_compat32.h"
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/kmod.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/vmalloc.h>
+#include <linux/fs.h>
+#include "comedidev.h"
+#include <linux/cdev.h>
+#include <linux/stat.h>
+
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include "comedi_internal.h"
+
+/**
+ * struct comedi_file - per-file private data for comedi device
+ * @dev: comedi_device struct
+ * @read_subdev: current "read" subdevice
+ * @write_subdev: current "write" subdevice
+ * @last_detach_count: last known detach count
+ * @last_attached: last known attached/detached state
+ */
+struct comedi_file {
+ struct comedi_device *dev;
+ struct comedi_subdevice *read_subdev;
+ struct comedi_subdevice *write_subdev;
+ unsigned int last_detach_count;
+ bool last_attached:1;
+};
+
+#define COMEDI_NUM_MINORS 0x100
+#define COMEDI_NUM_SUBDEVICE_MINORS \
+ (COMEDI_NUM_MINORS - COMEDI_NUM_BOARD_MINORS)
+
+static int comedi_num_legacy_minors;
+module_param(comedi_num_legacy_minors, int, S_IRUGO);
+MODULE_PARM_DESC(comedi_num_legacy_minors,
+ "number of comedi minor devices to reserve for non-auto-configured devices (default 0)"
+ );
+
+unsigned int comedi_default_buf_size_kb = CONFIG_COMEDI_DEFAULT_BUF_SIZE_KB;
+module_param(comedi_default_buf_size_kb, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(comedi_default_buf_size_kb,
+ "default asynchronous buffer size in KiB (default "
+ __MODULE_STRING(CONFIG_COMEDI_DEFAULT_BUF_SIZE_KB) ")");
+
+unsigned int comedi_default_buf_maxsize_kb
+ = CONFIG_COMEDI_DEFAULT_BUF_MAXSIZE_KB;
+module_param(comedi_default_buf_maxsize_kb, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(comedi_default_buf_maxsize_kb,
+ "default maximum size of asynchronous buffer in KiB (default "
+ __MODULE_STRING(CONFIG_COMEDI_DEFAULT_BUF_MAXSIZE_KB) ")");
+
+static DEFINE_MUTEX(comedi_board_minor_table_lock);
+static struct comedi_device
+*comedi_board_minor_table[COMEDI_NUM_BOARD_MINORS];
+
+static DEFINE_MUTEX(comedi_subdevice_minor_table_lock);
+/* Note: indexed by minor - COMEDI_NUM_BOARD_MINORS. */
+static struct comedi_subdevice
+*comedi_subdevice_minor_table[COMEDI_NUM_SUBDEVICE_MINORS];
+
+static struct class *comedi_class;
+static struct cdev comedi_cdev;
+
+static void comedi_device_init(struct comedi_device *dev)
+{
+ kref_init(&dev->refcount);
+ spin_lock_init(&dev->spinlock);
+ mutex_init(&dev->mutex);
+ init_rwsem(&dev->attach_lock);
+ dev->minor = -1;
+}
+
+static void comedi_dev_kref_release(struct kref *kref)
+{
+ struct comedi_device *dev =
+ container_of(kref, struct comedi_device, refcount);
+
+ mutex_destroy(&dev->mutex);
+ put_device(dev->class_dev);
+ kfree(dev);
+}
+
+/**
+ * comedi_dev_put - release a use of a comedi device structure
+ * @dev: comedi_device struct
+ *
+ * Must be called when a user of a comedi device is finished with it.
+ * When the last user of the comedi device calls this function, the
+ * comedi device is destroyed.
+ *
+ * Return 1 if the comedi device is destroyed by this call or dev is
+ * NULL, otherwise return 0. Callers must not assume the comedi
+ * device is still valid if this function returns 0.
+ */
+int comedi_dev_put(struct comedi_device *dev)
+{
+ if (dev)
+ return kref_put(&dev->refcount, comedi_dev_kref_release);
+ return 1;
+}
+EXPORT_SYMBOL_GPL(comedi_dev_put);
+
+static struct comedi_device *comedi_dev_get(struct comedi_device *dev)
+{
+ if (dev)
+ kref_get(&dev->refcount);
+ return dev;
+}
+
+static void comedi_device_cleanup(struct comedi_device *dev)
+{
+ struct module *driver_module = NULL;
+
+ if (!dev)
+ return;
+ mutex_lock(&dev->mutex);
+ if (dev->attached)
+ driver_module = dev->driver->module;
+ comedi_device_detach(dev);
+ if (driver_module && dev->use_count)
+ module_put(driver_module);
+ mutex_unlock(&dev->mutex);
+}
+
+static bool comedi_clear_board_dev(struct comedi_device *dev)
+{
+ unsigned int i = dev->minor;
+ bool cleared = false;
+
+ mutex_lock(&comedi_board_minor_table_lock);
+ if (dev == comedi_board_minor_table[i]) {
+ comedi_board_minor_table[i] = NULL;
+ cleared = true;
+ }
+ mutex_unlock(&comedi_board_minor_table_lock);
+ return cleared;
+}
+
+static struct comedi_device *comedi_clear_board_minor(unsigned minor)
+{
+ struct comedi_device *dev;
+
+ mutex_lock(&comedi_board_minor_table_lock);
+ dev = comedi_board_minor_table[minor];
+ comedi_board_minor_table[minor] = NULL;
+ mutex_unlock(&comedi_board_minor_table_lock);
+ return dev;
+}
+
+static void comedi_free_board_dev(struct comedi_device *dev)
+{
+ if (dev) {
+ comedi_device_cleanup(dev);
+ if (dev->class_dev) {
+ device_destroy(comedi_class,
+ MKDEV(COMEDI_MAJOR, dev->minor));
+ }
+ comedi_dev_put(dev);
+ }
+}
+
+static struct comedi_subdevice
+*comedi_subdevice_from_minor(const struct comedi_device *dev, unsigned minor)
+{
+ struct comedi_subdevice *s;
+ unsigned int i = minor - COMEDI_NUM_BOARD_MINORS;
+
+ BUG_ON(i >= COMEDI_NUM_SUBDEVICE_MINORS);
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ s = comedi_subdevice_minor_table[i];
+ if (s && s->device != dev)
+ s = NULL;
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ return s;
+}
+
+static struct comedi_device *comedi_dev_get_from_board_minor(unsigned minor)
+{
+ struct comedi_device *dev;
+
+ BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS);
+ mutex_lock(&comedi_board_minor_table_lock);
+ dev = comedi_dev_get(comedi_board_minor_table[minor]);
+ mutex_unlock(&comedi_board_minor_table_lock);
+ return dev;
+}
+
+static struct comedi_device *comedi_dev_get_from_subdevice_minor(unsigned minor)
+{
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int i = minor - COMEDI_NUM_BOARD_MINORS;
+
+ BUG_ON(i >= COMEDI_NUM_SUBDEVICE_MINORS);
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ s = comedi_subdevice_minor_table[i];
+ dev = comedi_dev_get(s ? s->device : NULL);
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ return dev;
+}
+
+/**
+ * comedi_dev_get_from_minor - get comedi device by minor device number
+ * @minor: minor device number
+ *
+ * Finds the comedi device associated by the minor device number, if any,
+ * and increments its reference count. The comedi device is prevented from
+ * being freed until a matching call is made to comedi_dev_put().
+ *
+ * Return a pointer to the comedi device if it exists, with its usage
+ * reference incremented. Return NULL if no comedi device exists with the
+ * specified minor device number.
+ */
+struct comedi_device *comedi_dev_get_from_minor(unsigned minor)
+{
+ if (minor < COMEDI_NUM_BOARD_MINORS)
+ return comedi_dev_get_from_board_minor(minor);
+
+ return comedi_dev_get_from_subdevice_minor(minor);
+}
+EXPORT_SYMBOL_GPL(comedi_dev_get_from_minor);
+
+static struct comedi_subdevice *
+comedi_read_subdevice(const struct comedi_device *dev, unsigned int minor)
+{
+ struct comedi_subdevice *s;
+
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ s = comedi_subdevice_from_minor(dev, minor);
+ if (!s || (s->subdev_flags & SDF_CMD_READ))
+ return s;
+ }
+ return dev->read_subdev;
+}
+
+static struct comedi_subdevice *
+comedi_write_subdevice(const struct comedi_device *dev, unsigned int minor)
+{
+ struct comedi_subdevice *s;
+
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ s = comedi_subdevice_from_minor(dev, minor);
+ if (!s || (s->subdev_flags & SDF_CMD_WRITE))
+ return s;
+ }
+ return dev->write_subdev;
+}
+
+static void comedi_file_reset(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ struct comedi_subdevice *s, *read_s, *write_s;
+ unsigned int minor = iminor(file_inode(file));
+
+ read_s = dev->read_subdev;
+ write_s = dev->write_subdev;
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ s = comedi_subdevice_from_minor(dev, minor);
+ if (!s || s->subdev_flags & SDF_CMD_READ)
+ read_s = s;
+ if (!s || s->subdev_flags & SDF_CMD_WRITE)
+ write_s = s;
+ }
+ cfp->last_attached = dev->attached;
+ cfp->last_detach_count = dev->detach_count;
+ ACCESS_ONCE(cfp->read_subdev) = read_s;
+ ACCESS_ONCE(cfp->write_subdev) = write_s;
+}
+
+static void comedi_file_check(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+
+ if (cfp->last_attached != dev->attached ||
+ cfp->last_detach_count != dev->detach_count)
+ comedi_file_reset(file);
+}
+
+static struct comedi_subdevice *comedi_file_read_subdevice(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+
+ comedi_file_check(file);
+ return ACCESS_ONCE(cfp->read_subdev);
+}
+
+static struct comedi_subdevice *comedi_file_write_subdevice(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+
+ comedi_file_check(file);
+ return ACCESS_ONCE(cfp->write_subdev);
+}
+
+static int resize_async_buffer(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned new_size)
+{
+ struct comedi_async *async = s->async;
+ int retval;
+
+ if (new_size > async->max_bufsize)
+ return -EPERM;
+
+ if (s->busy) {
+ dev_dbg(dev->class_dev,
+ "subdevice is busy, cannot resize buffer\n");
+ return -EBUSY;
+ }
+ if (comedi_buf_is_mmapped(s)) {
+ dev_dbg(dev->class_dev,
+ "subdevice is mmapped, cannot resize buffer\n");
+ return -EBUSY;
+ }
+
+ /* make sure buffer is an integral number of pages (we round up) */
+ new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK;
+
+ retval = comedi_buf_alloc(dev, s, new_size);
+ if (retval < 0)
+ return retval;
+
+ if (s->buf_change) {
+ retval = s->buf_change(dev, s);
+ if (retval < 0)
+ return retval;
+ }
+
+ dev_dbg(dev->class_dev, "subd %d buffer resized to %i bytes\n",
+ s->index, async->prealloc_bufsz);
+ return 0;
+}
+
+/* sysfs attribute files */
+
+static ssize_t max_read_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ size = s->async->max_bufsize / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t max_read_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ s->async->max_bufsize = size;
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(max_read_buffer_kb);
+
+static ssize_t read_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ size = s->async->prealloc_bufsz / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t read_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ err = resize_async_buffer(dev, s, size);
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(read_buffer_kb);
+
+static ssize_t max_write_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ size = s->async->max_bufsize / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t max_write_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ s->async->max_bufsize = size;
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(max_write_buffer_kb);
+
+static ssize_t write_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ size = s->async->prealloc_bufsz / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t write_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ err = resize_async_buffer(dev, s, size);
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(write_buffer_kb);
+
+static struct attribute *comedi_dev_attrs[] = {
+ &dev_attr_max_read_buffer_kb.attr,
+ &dev_attr_read_buffer_kb.attr,
+ &dev_attr_max_write_buffer_kb.attr,
+ &dev_attr_write_buffer_kb.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(comedi_dev);
+
+static void __comedi_clear_subdevice_runflags(struct comedi_subdevice *s,
+ unsigned bits)
+{
+ s->runflags &= ~bits;
+}
+
+static void __comedi_set_subdevice_runflags(struct comedi_subdevice *s,
+ unsigned bits)
+{
+ s->runflags |= bits;
+}
+
+static void comedi_update_subdevice_runflags(struct comedi_subdevice *s,
+ unsigned mask, unsigned bits)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+ __comedi_clear_subdevice_runflags(s, mask);
+ __comedi_set_subdevice_runflags(s, bits & mask);
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+}
+
+static unsigned __comedi_get_subdevice_runflags(struct comedi_subdevice *s)
+{
+ return s->runflags;
+}
+
+static unsigned comedi_get_subdevice_runflags(struct comedi_subdevice *s)
+{
+ unsigned long flags;
+ unsigned runflags;
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+ runflags = __comedi_get_subdevice_runflags(s);
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+ return runflags;
+}
+
+static bool comedi_is_runflags_running(unsigned runflags)
+{
+ return runflags & COMEDI_SRF_RUNNING;
+}
+
+static bool comedi_is_runflags_in_error(unsigned runflags)
+{
+ return runflags & COMEDI_SRF_ERROR;
+}
+
+/**
+ * comedi_is_subdevice_running - check if async command running on subdevice
+ * @s: comedi_subdevice struct
+ *
+ * Return true if an asynchronous comedi command is active on the comedi
+ * subdevice, else return false.
+ */
+bool comedi_is_subdevice_running(struct comedi_subdevice *s)
+{
+ unsigned runflags = comedi_get_subdevice_runflags(s);
+
+ return comedi_is_runflags_running(runflags);
+}
+EXPORT_SYMBOL_GPL(comedi_is_subdevice_running);
+
+static bool __comedi_is_subdevice_running(struct comedi_subdevice *s)
+{
+ unsigned runflags = __comedi_get_subdevice_runflags(s);
+
+ return comedi_is_runflags_running(runflags);
+}
+
+static bool comedi_is_subdevice_idle(struct comedi_subdevice *s)
+{
+ unsigned runflags = comedi_get_subdevice_runflags(s);
+
+ return !(runflags & COMEDI_SRF_BUSY_MASK);
+}
+
+/**
+ * comedi_alloc_spriv() - Allocate memory for the subdevice private data.
+ * @s: comedi_subdevice struct
+ * @size: size of the memory to allocate
+ *
+ * This also sets the subdevice runflags to allow the core to automatically
+ * free the private data during the detach.
+ */
+void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size)
+{
+ s->private = kzalloc(size, GFP_KERNEL);
+ if (s->private)
+ s->runflags |= COMEDI_SRF_FREE_SPRIV;
+ return s->private;
+}
+EXPORT_SYMBOL_GPL(comedi_alloc_spriv);
+
+/*
+ * This function restores a subdevice to an idle state.
+ */
+static void do_become_nonbusy(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+
+ comedi_update_subdevice_runflags(s, COMEDI_SRF_RUNNING, 0);
+ if (async) {
+ comedi_buf_reset(s);
+ async->inttrig = NULL;
+ kfree(async->cmd.chanlist);
+ async->cmd.chanlist = NULL;
+ s->busy = NULL;
+ wake_up_interruptible_all(&async->wait_head);
+ } else {
+ dev_err(dev->class_dev,
+ "BUG: (?) do_become_nonbusy called with async=NULL\n");
+ s->busy = NULL;
+ }
+}
+
+static int do_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ int ret = 0;
+
+ if (comedi_is_subdevice_running(s) && s->cancel)
+ ret = s->cancel(dev, s);
+
+ do_become_nonbusy(dev, s);
+
+ return ret;
+}
+
+void comedi_device_cancel_all(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int i;
+
+ if (!dev->attached)
+ return;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (s->async)
+ do_cancel(dev, s);
+ }
+}
+
+static int is_device_busy(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int i;
+
+ if (!dev->attached)
+ return 0;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (s->busy)
+ return 1;
+ if (s->async && comedi_buf_is_mmapped(s))
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * COMEDI_DEVCONFIG ioctl
+ * attaches (and configures) or detaches a legacy device
+ *
+ * arg:
+ * pointer to comedi_devconfig structure (NULL if detaching)
+ *
+ * reads:
+ * comedi_devconfig structure (if attaching)
+ *
+ * writes:
+ * nothing
+ */
+static int do_devconfig_ioctl(struct comedi_device *dev,
+ struct comedi_devconfig __user *arg)
+{
+ struct comedi_devconfig it;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (!arg) {
+ if (is_device_busy(dev))
+ return -EBUSY;
+ if (dev->attached) {
+ struct module *driver_module = dev->driver->module;
+
+ comedi_device_detach(dev);
+ module_put(driver_module);
+ }
+ return 0;
+ }
+
+ if (copy_from_user(&it, arg, sizeof(it)))
+ return -EFAULT;
+
+ it.board_name[COMEDI_NAMELEN - 1] = 0;
+
+ if (it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) {
+ dev_warn(dev->class_dev,
+ "comedi_config --init_data is deprecated\n");
+ return -EINVAL;
+ }
+
+ if (dev->minor >= comedi_num_legacy_minors)
+ /* don't re-use dynamically allocated comedi devices */
+ return -EBUSY;
+
+ /* This increments the driver module count on success. */
+ return comedi_device_attach(dev, &it);
+}
+
+/*
+ * COMEDI_BUFCONFIG ioctl
+ * buffer configuration
+ *
+ * arg:
+ * pointer to comedi_bufconfig structure
+ *
+ * reads:
+ * comedi_bufconfig structure
+ *
+ * writes:
+ * modified comedi_bufconfig structure
+ */
+static int do_bufconfig_ioctl(struct comedi_device *dev,
+ struct comedi_bufconfig __user *arg)
+{
+ struct comedi_bufconfig bc;
+ struct comedi_async *async;
+ struct comedi_subdevice *s;
+ int retval = 0;
+
+ if (copy_from_user(&bc, arg, sizeof(bc)))
+ return -EFAULT;
+
+ if (bc.subdevice >= dev->n_subdevices)
+ return -EINVAL;
+
+ s = &dev->subdevices[bc.subdevice];
+ async = s->async;
+
+ if (!async) {
+ dev_dbg(dev->class_dev,
+ "subdevice does not have async capability\n");
+ bc.size = 0;
+ bc.maximum_size = 0;
+ goto copyback;
+ }
+
+ if (bc.maximum_size) {
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ async->max_bufsize = bc.maximum_size;
+ }
+
+ if (bc.size) {
+ retval = resize_async_buffer(dev, s, bc.size);
+ if (retval < 0)
+ return retval;
+ }
+
+ bc.size = async->prealloc_bufsz;
+ bc.maximum_size = async->max_bufsize;
+
+copyback:
+ if (copy_to_user(arg, &bc, sizeof(bc)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * COMEDI_DEVINFO ioctl
+ * device info
+ *
+ * arg:
+ * pointer to comedi_devinfo structure
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * comedi_devinfo structure
+ */
+static int do_devinfo_ioctl(struct comedi_device *dev,
+ struct comedi_devinfo __user *arg,
+ struct file *file)
+{
+ struct comedi_subdevice *s;
+ struct comedi_devinfo devinfo;
+
+ memset(&devinfo, 0, sizeof(devinfo));
+
+ /* fill devinfo structure */
+ devinfo.version_code = COMEDI_VERSION_CODE;
+ devinfo.n_subdevs = dev->n_subdevices;
+ strlcpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN);
+ strlcpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN);
+
+ s = comedi_file_read_subdevice(file);
+ if (s)
+ devinfo.read_subdevice = s->index;
+ else
+ devinfo.read_subdevice = -1;
+
+ s = comedi_file_write_subdevice(file);
+ if (s)
+ devinfo.write_subdevice = s->index;
+ else
+ devinfo.write_subdevice = -1;
+
+ if (copy_to_user(arg, &devinfo, sizeof(devinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * COMEDI_SUBDINFO ioctl
+ * subdevices info
+ *
+ * arg:
+ * pointer to array of comedi_subdinfo structures
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * array of comedi_subdinfo structures
+ */
+static int do_subdinfo_ioctl(struct comedi_device *dev,
+ struct comedi_subdinfo __user *arg, void *file)
+{
+ int ret, i;
+ struct comedi_subdinfo *tmp, *us;
+ struct comedi_subdevice *s;
+
+ tmp = kcalloc(dev->n_subdevices, sizeof(*tmp), GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ /* fill subdinfo structs */
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ us = tmp + i;
+
+ us->type = s->type;
+ us->n_chan = s->n_chan;
+ us->subd_flags = s->subdev_flags;
+ if (comedi_is_subdevice_running(s))
+ us->subd_flags |= SDF_RUNNING;
+#define TIMER_nanosec 5 /* backwards compatibility */
+ us->timer_type = TIMER_nanosec;
+ us->len_chanlist = s->len_chanlist;
+ us->maxdata = s->maxdata;
+ if (s->range_table) {
+ us->range_type =
+ (i << 24) | (0 << 16) | (s->range_table->length);
+ } else {
+ us->range_type = 0; /* XXX */
+ }
+
+ if (s->busy)
+ us->subd_flags |= SDF_BUSY;
+ if (s->busy == file)
+ us->subd_flags |= SDF_BUSY_OWNER;
+ if (s->lock)
+ us->subd_flags |= SDF_LOCKED;
+ if (s->lock == file)
+ us->subd_flags |= SDF_LOCK_OWNER;
+ if (!s->maxdata && s->maxdata_list)
+ us->subd_flags |= SDF_MAXDATA;
+ if (s->range_table_list)
+ us->subd_flags |= SDF_RANGETYPE;
+ if (s->do_cmd)
+ us->subd_flags |= SDF_CMD;
+
+ if (s->insn_bits != &insn_inval)
+ us->insn_bits_support = COMEDI_SUPPORTED;
+ else
+ us->insn_bits_support = COMEDI_UNSUPPORTED;
+ }
+
+ ret = copy_to_user(arg, tmp, dev->n_subdevices * sizeof(*tmp));
+
+ kfree(tmp);
+
+ return ret ? -EFAULT : 0;
+}
+
+/*
+ * COMEDI_CHANINFO ioctl
+ * subdevice channel info
+ *
+ * arg:
+ * pointer to comedi_chaninfo structure
+ *
+ * reads:
+ * comedi_chaninfo structure
+ *
+ * writes:
+ * array of maxdata values to chaninfo->maxdata_list if requested
+ * array of range table lengths to chaninfo->range_table_list if requested
+ */
+static int do_chaninfo_ioctl(struct comedi_device *dev,
+ struct comedi_chaninfo __user *arg)
+{
+ struct comedi_subdevice *s;
+ struct comedi_chaninfo it;
+
+ if (copy_from_user(&it, arg, sizeof(it)))
+ return -EFAULT;
+
+ if (it.subdev >= dev->n_subdevices)
+ return -EINVAL;
+ s = &dev->subdevices[it.subdev];
+
+ if (it.maxdata_list) {
+ if (s->maxdata || !s->maxdata_list)
+ return -EINVAL;
+ if (copy_to_user(it.maxdata_list, s->maxdata_list,
+ s->n_chan * sizeof(unsigned int)))
+ return -EFAULT;
+ }
+
+ if (it.flaglist)
+ return -EINVAL; /* flaglist not supported */
+
+ if (it.rangelist) {
+ int i;
+
+ if (!s->range_table_list)
+ return -EINVAL;
+ for (i = 0; i < s->n_chan; i++) {
+ int x;
+
+ x = (dev->minor << 28) | (it.subdev << 24) | (i << 16) |
+ (s->range_table_list[i]->length);
+ if (put_user(x, it.rangelist + i))
+ return -EFAULT;
+ }
+#if 0
+ if (copy_to_user(it.rangelist, s->range_type_list,
+ s->n_chan * sizeof(unsigned int)))
+ return -EFAULT;
+#endif
+ }
+
+ return 0;
+}
+
+/*
+ * COMEDI_BUFINFO ioctl
+ * buffer information
+ *
+ * arg:
+ * pointer to comedi_bufinfo structure
+ *
+ * reads:
+ * comedi_bufinfo structure
+ *
+ * writes:
+ * modified comedi_bufinfo structure
+ */
+static int do_bufinfo_ioctl(struct comedi_device *dev,
+ struct comedi_bufinfo __user *arg, void *file)
+{
+ struct comedi_bufinfo bi;
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+
+ if (copy_from_user(&bi, arg, sizeof(bi)))
+ return -EFAULT;
+
+ if (bi.subdevice >= dev->n_subdevices)
+ return -EINVAL;
+
+ s = &dev->subdevices[bi.subdevice];
+
+ async = s->async;
+
+ if (!async) {
+ dev_dbg(dev->class_dev,
+ "subdevice does not have async capability\n");
+ bi.buf_write_ptr = 0;
+ bi.buf_read_ptr = 0;
+ bi.buf_write_count = 0;
+ bi.buf_read_count = 0;
+ bi.bytes_read = 0;
+ bi.bytes_written = 0;
+ goto copyback;
+ }
+ if (!s->busy) {
+ bi.bytes_read = 0;
+ bi.bytes_written = 0;
+ goto copyback_position;
+ }
+ if (s->busy != file)
+ return -EACCES;
+
+ if (bi.bytes_read && !(async->cmd.flags & CMDF_WRITE)) {
+ bi.bytes_read = comedi_buf_read_alloc(s, bi.bytes_read);
+ comedi_buf_read_free(s, bi.bytes_read);
+
+ if (comedi_is_subdevice_idle(s) &&
+ comedi_buf_n_bytes_ready(s) == 0) {
+ do_become_nonbusy(dev, s);
+ }
+ }
+
+ if (bi.bytes_written && (async->cmd.flags & CMDF_WRITE)) {
+ bi.bytes_written =
+ comedi_buf_write_alloc(s, bi.bytes_written);
+ comedi_buf_write_free(s, bi.bytes_written);
+ }
+
+copyback_position:
+ bi.buf_write_count = async->buf_write_count;
+ bi.buf_write_ptr = async->buf_write_ptr;
+ bi.buf_read_count = async->buf_read_count;
+ bi.buf_read_ptr = async->buf_read_ptr;
+
+copyback:
+ if (copy_to_user(arg, &bi, sizeof(bi)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int check_insn_config_length(struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (insn->n < 1)
+ return -EINVAL;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIO_OUTPUT:
+ case INSN_CONFIG_DIO_INPUT:
+ case INSN_CONFIG_DISARM:
+ case INSN_CONFIG_RESET:
+ if (insn->n == 1)
+ return 0;
+ break;
+ case INSN_CONFIG_ARM:
+ case INSN_CONFIG_DIO_QUERY:
+ case INSN_CONFIG_BLOCK_SIZE:
+ case INSN_CONFIG_FILTER:
+ case INSN_CONFIG_SERIAL_CLOCK:
+ case INSN_CONFIG_BIDIRECTIONAL_DATA:
+ case INSN_CONFIG_ALT_SOURCE:
+ case INSN_CONFIG_SET_COUNTER_MODE:
+ case INSN_CONFIG_8254_READ_STATUS:
+ case INSN_CONFIG_SET_ROUTING:
+ case INSN_CONFIG_GET_ROUTING:
+ case INSN_CONFIG_GET_PWM_STATUS:
+ case INSN_CONFIG_PWM_SET_PERIOD:
+ case INSN_CONFIG_PWM_GET_PERIOD:
+ if (insn->n == 2)
+ return 0;
+ break;
+ case INSN_CONFIG_SET_GATE_SRC:
+ case INSN_CONFIG_GET_GATE_SRC:
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ case INSN_CONFIG_SET_OTHER_SRC:
+ case INSN_CONFIG_GET_COUNTER_STATUS:
+ case INSN_CONFIG_PWM_SET_H_BRIDGE:
+ case INSN_CONFIG_PWM_GET_H_BRIDGE:
+ case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE:
+ if (insn->n == 3)
+ return 0;
+ break;
+ case INSN_CONFIG_PWM_OUTPUT:
+ case INSN_CONFIG_ANALOG_TRIG:
+ if (insn->n == 5)
+ return 0;
+ break;
+ case INSN_CONFIG_DIGITAL_TRIG:
+ if (insn->n == 6)
+ return 0;
+ break;
+ /*
+ * by default we allow the insn since we don't have checks for
+ * all possible cases yet
+ */
+ default:
+ pr_warn("No check for data length of config insn id %i is implemented\n",
+ data[0]);
+ pr_warn("Add a check to %s in %s\n", __func__, __FILE__);
+ pr_warn("Assuming n=%i is correct\n", insn->n);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int parse_insn(struct comedi_device *dev, struct comedi_insn *insn,
+ unsigned int *data, void *file)
+{
+ struct comedi_subdevice *s;
+ int ret = 0;
+ int i;
+
+ if (insn->insn & INSN_MASK_SPECIAL) {
+ /* a non-subdevice instruction */
+
+ switch (insn->insn) {
+ case INSN_GTOD:
+ {
+ struct timeval tv;
+
+ if (insn->n != 2) {
+ ret = -EINVAL;
+ break;
+ }
+
+ do_gettimeofday(&tv);
+ data[0] = tv.tv_sec;
+ data[1] = tv.tv_usec;
+ ret = 2;
+
+ break;
+ }
+ case INSN_WAIT:
+ if (insn->n != 1 || data[0] >= 100000) {
+ ret = -EINVAL;
+ break;
+ }
+ udelay(data[0] / 1000);
+ ret = 1;
+ break;
+ case INSN_INTTRIG:
+ if (insn->n != 1) {
+ ret = -EINVAL;
+ break;
+ }
+ if (insn->subdev >= dev->n_subdevices) {
+ dev_dbg(dev->class_dev,
+ "%d not usable subdevice\n",
+ insn->subdev);
+ ret = -EINVAL;
+ break;
+ }
+ s = &dev->subdevices[insn->subdev];
+ if (!s->async) {
+ dev_dbg(dev->class_dev, "no async\n");
+ ret = -EINVAL;
+ break;
+ }
+ if (!s->async->inttrig) {
+ dev_dbg(dev->class_dev, "no inttrig\n");
+ ret = -EAGAIN;
+ break;
+ }
+ ret = s->async->inttrig(dev, s, data[0]);
+ if (ret >= 0)
+ ret = 1;
+ break;
+ default:
+ dev_dbg(dev->class_dev, "invalid insn\n");
+ ret = -EINVAL;
+ break;
+ }
+ } else {
+ /* a subdevice instruction */
+ unsigned int maxdata;
+
+ if (insn->subdev >= dev->n_subdevices) {
+ dev_dbg(dev->class_dev, "subdevice %d out of range\n",
+ insn->subdev);
+ ret = -EINVAL;
+ goto out;
+ }
+ s = &dev->subdevices[insn->subdev];
+
+ if (s->type == COMEDI_SUBD_UNUSED) {
+ dev_dbg(dev->class_dev, "%d not usable subdevice\n",
+ insn->subdev);
+ ret = -EIO;
+ goto out;
+ }
+
+ /* are we locked? (ioctl lock) */
+ if (s->lock && s->lock != file) {
+ dev_dbg(dev->class_dev, "device locked\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ ret = comedi_check_chanlist(s, 1, &insn->chanspec);
+ if (ret < 0) {
+ ret = -EINVAL;
+ dev_dbg(dev->class_dev, "bad chanspec\n");
+ goto out;
+ }
+
+ if (s->busy) {
+ ret = -EBUSY;
+ goto out;
+ }
+ /* This looks arbitrary. It is. */
+ s->busy = &parse_insn;
+ switch (insn->insn) {
+ case INSN_READ:
+ ret = s->insn_read(dev, s, insn, data);
+ if (ret == -ETIMEDOUT) {
+ dev_dbg(dev->class_dev,
+ "subdevice %d read instruction timed out\n",
+ s->index);
+ }
+ break;
+ case INSN_WRITE:
+ maxdata = s->maxdata_list
+ ? s->maxdata_list[CR_CHAN(insn->chanspec)]
+ : s->maxdata;
+ for (i = 0; i < insn->n; ++i) {
+ if (data[i] > maxdata) {
+ ret = -EINVAL;
+ dev_dbg(dev->class_dev,
+ "bad data value(s)\n");
+ break;
+ }
+ }
+ if (ret == 0) {
+ ret = s->insn_write(dev, s, insn, data);
+ if (ret == -ETIMEDOUT) {
+ dev_dbg(dev->class_dev,
+ "subdevice %d write instruction timed out\n",
+ s->index);
+ }
+ }
+ break;
+ case INSN_BITS:
+ if (insn->n != 2) {
+ ret = -EINVAL;
+ } else {
+ /*
+ * Most drivers ignore the base channel in
+ * insn->chanspec. Fix this here if
+ * the subdevice has <= 32 channels.
+ */
+ unsigned int orig_mask = data[0];
+ unsigned int shift = 0;
+
+ if (s->n_chan <= 32) {
+ shift = CR_CHAN(insn->chanspec);
+ if (shift > 0) {
+ insn->chanspec = 0;
+ data[0] <<= shift;
+ data[1] <<= shift;
+ }
+ }
+ ret = s->insn_bits(dev, s, insn, data);
+ data[0] = orig_mask;
+ if (shift > 0)
+ data[1] >>= shift;
+ }
+ break;
+ case INSN_CONFIG:
+ ret = check_insn_config_length(insn, data);
+ if (ret)
+ break;
+ ret = s->insn_config(dev, s, insn, data);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ s->busy = NULL;
+ }
+
+out:
+ return ret;
+}
+
+/*
+ * COMEDI_INSNLIST ioctl
+ * synchronous instruction list
+ *
+ * arg:
+ * pointer to comedi_insnlist structure
+ *
+ * reads:
+ * comedi_insnlist structure
+ * array of comedi_insn structures from insnlist->insns pointer
+ * data (for writes) from insns[].data pointers
+ *
+ * writes:
+ * data (for reads) to insns[].data pointers
+ */
+/* arbitrary limits */
+#define MAX_SAMPLES 256
+static int do_insnlist_ioctl(struct comedi_device *dev,
+ struct comedi_insnlist __user *arg, void *file)
+{
+ struct comedi_insnlist insnlist;
+ struct comedi_insn *insns = NULL;
+ unsigned int *data = NULL;
+ int i = 0;
+ int ret = 0;
+
+ if (copy_from_user(&insnlist, arg, sizeof(insnlist)))
+ return -EFAULT;
+
+ data = kmalloc_array(MAX_SAMPLES, sizeof(unsigned int), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ insns = kcalloc(insnlist.n_insns, sizeof(*insns), GFP_KERNEL);
+ if (!insns) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (copy_from_user(insns, insnlist.insns,
+ sizeof(*insns) * insnlist.n_insns)) {
+ dev_dbg(dev->class_dev, "copy_from_user failed\n");
+ ret = -EFAULT;
+ goto error;
+ }
+
+ for (i = 0; i < insnlist.n_insns; i++) {
+ if (insns[i].n > MAX_SAMPLES) {
+ dev_dbg(dev->class_dev,
+ "number of samples too large\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ if (insns[i].insn & INSN_MASK_WRITE) {
+ if (copy_from_user(data, insns[i].data,
+ insns[i].n * sizeof(unsigned int))) {
+ dev_dbg(dev->class_dev,
+ "copy_from_user failed\n");
+ ret = -EFAULT;
+ goto error;
+ }
+ }
+ ret = parse_insn(dev, insns + i, data, file);
+ if (ret < 0)
+ goto error;
+ if (insns[i].insn & INSN_MASK_READ) {
+ if (copy_to_user(insns[i].data, data,
+ insns[i].n * sizeof(unsigned int))) {
+ dev_dbg(dev->class_dev,
+ "copy_to_user failed\n");
+ ret = -EFAULT;
+ goto error;
+ }
+ }
+ if (need_resched())
+ schedule();
+ }
+
+error:
+ kfree(insns);
+ kfree(data);
+
+ if (ret < 0)
+ return ret;
+ return i;
+}
+
+/*
+ * COMEDI_INSN ioctl
+ * synchronous instruction
+ *
+ * arg:
+ * pointer to comedi_insn structure
+ *
+ * reads:
+ * comedi_insn structure
+ * data (for writes) from insn->data pointer
+ *
+ * writes:
+ * data (for reads) to insn->data pointer
+ */
+static int do_insn_ioctl(struct comedi_device *dev,
+ struct comedi_insn __user *arg, void *file)
+{
+ struct comedi_insn insn;
+ unsigned int *data = NULL;
+ int ret = 0;
+
+ data = kmalloc_array(MAX_SAMPLES, sizeof(unsigned int), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (copy_from_user(&insn, arg, sizeof(insn))) {
+ ret = -EFAULT;
+ goto error;
+ }
+
+ /* This is where the behavior of insn and insnlist deviate. */
+ if (insn.n > MAX_SAMPLES)
+ insn.n = MAX_SAMPLES;
+ if (insn.insn & INSN_MASK_WRITE) {
+ if (copy_from_user(data,
+ insn.data,
+ insn.n * sizeof(unsigned int))) {
+ ret = -EFAULT;
+ goto error;
+ }
+ }
+ ret = parse_insn(dev, &insn, data, file);
+ if (ret < 0)
+ goto error;
+ if (insn.insn & INSN_MASK_READ) {
+ if (copy_to_user(insn.data,
+ data,
+ insn.n * sizeof(unsigned int))) {
+ ret = -EFAULT;
+ goto error;
+ }
+ }
+ ret = insn.n;
+
+error:
+ kfree(data);
+
+ return ret;
+}
+
+static int __comedi_get_user_cmd(struct comedi_device *dev,
+ struct comedi_cmd __user *arg,
+ struct comedi_cmd *cmd)
+{
+ struct comedi_subdevice *s;
+
+ if (copy_from_user(cmd, arg, sizeof(*cmd))) {
+ dev_dbg(dev->class_dev, "bad cmd address\n");
+ return -EFAULT;
+ }
+
+ if (cmd->subdev >= dev->n_subdevices) {
+ dev_dbg(dev->class_dev, "%d no such subdevice\n", cmd->subdev);
+ return -ENODEV;
+ }
+
+ s = &dev->subdevices[cmd->subdev];
+
+ if (s->type == COMEDI_SUBD_UNUSED) {
+ dev_dbg(dev->class_dev, "%d not valid subdevice\n",
+ cmd->subdev);
+ return -EIO;
+ }
+
+ if (!s->do_cmd || !s->do_cmdtest || !s->async) {
+ dev_dbg(dev->class_dev,
+ "subdevice %d does not support commands\n",
+ cmd->subdev);
+ return -EIO;
+ }
+
+ /* make sure channel/gain list isn't too long */
+ if (cmd->chanlist_len > s->len_chanlist) {
+ dev_dbg(dev->class_dev, "channel/gain list too long %d > %d\n",
+ cmd->chanlist_len, s->len_chanlist);
+ return -EINVAL;
+ }
+
+ /*
+ * Set the CMDF_WRITE flag to the correct state if the subdevice
+ * supports only "read" commands or only "write" commands.
+ */
+ switch (s->subdev_flags & (SDF_CMD_READ | SDF_CMD_WRITE)) {
+ case SDF_CMD_READ:
+ cmd->flags &= ~CMDF_WRITE;
+ break;
+ case SDF_CMD_WRITE:
+ cmd->flags |= CMDF_WRITE;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int __comedi_get_user_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int __user *user_chanlist,
+ struct comedi_cmd *cmd)
+{
+ unsigned int *chanlist;
+ int ret;
+
+ cmd->chanlist = NULL;
+ chanlist = memdup_user(user_chanlist,
+ cmd->chanlist_len * sizeof(unsigned int));
+ if (IS_ERR(chanlist))
+ return PTR_ERR(chanlist);
+
+ /* make sure each element in channel/gain list is valid */
+ ret = comedi_check_chanlist(s, cmd->chanlist_len, chanlist);
+ if (ret < 0) {
+ kfree(chanlist);
+ return ret;
+ }
+
+ cmd->chanlist = chanlist;
+
+ return 0;
+}
+
+/*
+ * COMEDI_CMD ioctl
+ * asynchronous acquisition command set-up
+ *
+ * arg:
+ * pointer to comedi_cmd structure
+ *
+ * reads:
+ * comedi_cmd structure
+ * channel/range list from cmd->chanlist pointer
+ *
+ * writes:
+ * possibly modified comedi_cmd structure (when -EAGAIN returned)
+ */
+static int do_cmd_ioctl(struct comedi_device *dev,
+ struct comedi_cmd __user *arg, void *file)
+{
+ struct comedi_cmd cmd;
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+ unsigned int __user *user_chanlist;
+ int ret;
+
+ /* get the user's cmd and do some simple validation */
+ ret = __comedi_get_user_cmd(dev, arg, &cmd);
+ if (ret)
+ return ret;
+
+ /* save user's chanlist pointer so it can be restored later */
+ user_chanlist = (unsigned int __user *)cmd.chanlist;
+
+ s = &dev->subdevices[cmd.subdev];
+ async = s->async;
+
+ /* are we locked? (ioctl lock) */
+ if (s->lock && s->lock != file) {
+ dev_dbg(dev->class_dev, "subdevice locked\n");
+ return -EACCES;
+ }
+
+ /* are we busy? */
+ if (s->busy) {
+ dev_dbg(dev->class_dev, "subdevice busy\n");
+ return -EBUSY;
+ }
+
+ /* make sure channel/gain list isn't too short */
+ if (cmd.chanlist_len < 1) {
+ dev_dbg(dev->class_dev, "channel/gain list too short %u < 1\n",
+ cmd.chanlist_len);
+ return -EINVAL;
+ }
+
+ async->cmd = cmd;
+ async->cmd.data = NULL;
+
+ /* load channel/gain list */
+ ret = __comedi_get_user_chanlist(dev, s, user_chanlist, &async->cmd);
+ if (ret)
+ goto cleanup;
+
+ ret = s->do_cmdtest(dev, s, &async->cmd);
+
+ if (async->cmd.flags & CMDF_BOGUS || ret) {
+ dev_dbg(dev->class_dev, "test returned %d\n", ret);
+ cmd = async->cmd;
+ /* restore chanlist pointer before copying back */
+ cmd.chanlist = (unsigned int __force *)user_chanlist;
+ cmd.data = NULL;
+ if (copy_to_user(arg, &cmd, sizeof(cmd))) {
+ dev_dbg(dev->class_dev, "fault writing cmd\n");
+ ret = -EFAULT;
+ goto cleanup;
+ }
+ ret = -EAGAIN;
+ goto cleanup;
+ }
+
+ if (!async->prealloc_bufsz) {
+ ret = -ENOMEM;
+ dev_dbg(dev->class_dev, "no buffer (?)\n");
+ goto cleanup;
+ }
+
+ comedi_buf_reset(s);
+
+ async->cb_mask = COMEDI_CB_BLOCK | COMEDI_CB_CANCEL_MASK;
+ if (async->cmd.flags & CMDF_WAKE_EOS)
+ async->cb_mask |= COMEDI_CB_EOS;
+
+ comedi_update_subdevice_runflags(s, COMEDI_SRF_BUSY_MASK,
+ COMEDI_SRF_RUNNING);
+
+ /*
+ * Set s->busy _after_ setting COMEDI_SRF_RUNNING flag to avoid
+ * race with comedi_read() or comedi_write().
+ */
+ s->busy = file;
+ ret = s->do_cmd(dev, s);
+ if (ret == 0)
+ return 0;
+
+cleanup:
+ do_become_nonbusy(dev, s);
+
+ return ret;
+}
+
+/*
+ * COMEDI_CMDTEST ioctl
+ * asynchronous aquisition command testing
+ *
+ * arg:
+ * pointer to comedi_cmd structure
+ *
+ * reads:
+ * comedi_cmd structure
+ * channel/range list from cmd->chanlist pointer
+ *
+ * writes:
+ * possibly modified comedi_cmd structure
+ */
+static int do_cmdtest_ioctl(struct comedi_device *dev,
+ struct comedi_cmd __user *arg, void *file)
+{
+ struct comedi_cmd cmd;
+ struct comedi_subdevice *s;
+ unsigned int __user *user_chanlist;
+ int ret;
+
+ /* get the user's cmd and do some simple validation */
+ ret = __comedi_get_user_cmd(dev, arg, &cmd);
+ if (ret)
+ return ret;
+
+ /* save user's chanlist pointer so it can be restored later */
+ user_chanlist = (unsigned int __user *)cmd.chanlist;
+
+ s = &dev->subdevices[cmd.subdev];
+
+ /* user_chanlist can be NULL for COMEDI_CMDTEST ioctl */
+ if (user_chanlist) {
+ /* load channel/gain list */
+ ret = __comedi_get_user_chanlist(dev, s, user_chanlist, &cmd);
+ if (ret)
+ return ret;
+ }
+
+ ret = s->do_cmdtest(dev, s, &cmd);
+
+ kfree(cmd.chanlist); /* free kernel copy of user chanlist */
+
+ /* restore chanlist pointer before copying back */
+ cmd.chanlist = (unsigned int __force *)user_chanlist;
+
+ if (copy_to_user(arg, &cmd, sizeof(cmd))) {
+ dev_dbg(dev->class_dev, "bad cmd address\n");
+ ret = -EFAULT;
+ }
+
+ return ret;
+}
+
+/*
+ * COMEDI_LOCK ioctl
+ * lock subdevice
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_lock_ioctl(struct comedi_device *dev, unsigned long arg,
+ void *file)
+{
+ int ret = 0;
+ unsigned long flags;
+ struct comedi_subdevice *s;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+ s = &dev->subdevices[arg];
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+ if (s->busy || s->lock)
+ ret = -EBUSY;
+ else
+ s->lock = file;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+
+ return ret;
+}
+
+/*
+ * COMEDI_UNLOCK ioctl
+ * unlock subdevice
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_unlock_ioctl(struct comedi_device *dev, unsigned long arg,
+ void *file)
+{
+ struct comedi_subdevice *s;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+ s = &dev->subdevices[arg];
+
+ if (s->busy)
+ return -EBUSY;
+
+ if (s->lock && s->lock != file)
+ return -EACCES;
+
+ if (s->lock == file)
+ s->lock = NULL;
+
+ return 0;
+}
+
+/*
+ * COMEDI_CANCEL ioctl
+ * cancel asynchronous acquisition
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_cancel_ioctl(struct comedi_device *dev, unsigned long arg,
+ void *file)
+{
+ struct comedi_subdevice *s;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+ s = &dev->subdevices[arg];
+ if (!s->async)
+ return -EINVAL;
+
+ if (!s->busy)
+ return 0;
+
+ if (s->busy != file)
+ return -EBUSY;
+
+ return do_cancel(dev, s);
+}
+
+/*
+ * COMEDI_POLL ioctl
+ * instructs driver to synchronize buffers
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_poll_ioctl(struct comedi_device *dev, unsigned long arg,
+ void *file)
+{
+ struct comedi_subdevice *s;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+ s = &dev->subdevices[arg];
+
+ if (!s->busy)
+ return 0;
+
+ if (s->busy != file)
+ return -EBUSY;
+
+ if (s->poll)
+ return s->poll(dev, s);
+
+ return -EINVAL;
+}
+
+/*
+ * COMEDI_SETRSUBD ioctl
+ * sets the current "read" subdevice on a per-file basis
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_setrsubd_ioctl(struct comedi_device *dev, unsigned long arg,
+ struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_subdevice *s_old, *s_new;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+
+ s_new = &dev->subdevices[arg];
+ s_old = comedi_file_read_subdevice(file);
+ if (s_old == s_new)
+ return 0; /* no change */
+
+ if (!(s_new->subdev_flags & SDF_CMD_READ))
+ return -EINVAL;
+
+ /*
+ * Check the file isn't still busy handling a "read" command on the
+ * old subdevice (if any).
+ */
+ if (s_old && s_old->busy == file && s_old->async &&
+ !(s_old->async->cmd.flags & CMDF_WRITE))
+ return -EBUSY;
+
+ ACCESS_ONCE(cfp->read_subdev) = s_new;
+ return 0;
+}
+
+/*
+ * COMEDI_SETWSUBD ioctl
+ * sets the current "write" subdevice on a per-file basis
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_setwsubd_ioctl(struct comedi_device *dev, unsigned long arg,
+ struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_subdevice *s_old, *s_new;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+
+ s_new = &dev->subdevices[arg];
+ s_old = comedi_file_write_subdevice(file);
+ if (s_old == s_new)
+ return 0; /* no change */
+
+ if (!(s_new->subdev_flags & SDF_CMD_WRITE))
+ return -EINVAL;
+
+ /*
+ * Check the file isn't still busy handling a "write" command on the
+ * old subdevice (if any).
+ */
+ if (s_old && s_old->busy == file && s_old->async &&
+ (s_old->async->cmd.flags & CMDF_WRITE))
+ return -EBUSY;
+
+ ACCESS_ONCE(cfp->write_subdev) = s_new;
+ return 0;
+}
+
+static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ unsigned minor = iminor(file_inode(file));
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ int rc;
+
+ mutex_lock(&dev->mutex);
+
+ /*
+ * Device config is special, because it must work on
+ * an unconfigured device.
+ */
+ if (cmd == COMEDI_DEVCONFIG) {
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ /* Device config not appropriate on non-board minors. */
+ rc = -ENOTTY;
+ goto done;
+ }
+ rc = do_devconfig_ioctl(dev,
+ (struct comedi_devconfig __user *)arg);
+ if (rc == 0) {
+ if (arg == 0 &&
+ dev->minor >= comedi_num_legacy_minors) {
+ /*
+ * Successfully unconfigured a dynamically
+ * allocated device. Try and remove it.
+ */
+ if (comedi_clear_board_dev(dev)) {
+ mutex_unlock(&dev->mutex);
+ comedi_free_board_dev(dev);
+ return rc;
+ }
+ }
+ }
+ goto done;
+ }
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ rc = -ENODEV;
+ goto done;
+ }
+
+ switch (cmd) {
+ case COMEDI_BUFCONFIG:
+ rc = do_bufconfig_ioctl(dev,
+ (struct comedi_bufconfig __user *)arg);
+ break;
+ case COMEDI_DEVINFO:
+ rc = do_devinfo_ioctl(dev, (struct comedi_devinfo __user *)arg,
+ file);
+ break;
+ case COMEDI_SUBDINFO:
+ rc = do_subdinfo_ioctl(dev,
+ (struct comedi_subdinfo __user *)arg,
+ file);
+ break;
+ case COMEDI_CHANINFO:
+ rc = do_chaninfo_ioctl(dev, (void __user *)arg);
+ break;
+ case COMEDI_RANGEINFO:
+ rc = do_rangeinfo_ioctl(dev, (void __user *)arg);
+ break;
+ case COMEDI_BUFINFO:
+ rc = do_bufinfo_ioctl(dev,
+ (struct comedi_bufinfo __user *)arg,
+ file);
+ break;
+ case COMEDI_LOCK:
+ rc = do_lock_ioctl(dev, arg, file);
+ break;
+ case COMEDI_UNLOCK:
+ rc = do_unlock_ioctl(dev, arg, file);
+ break;
+ case COMEDI_CANCEL:
+ rc = do_cancel_ioctl(dev, arg, file);
+ break;
+ case COMEDI_CMD:
+ rc = do_cmd_ioctl(dev, (struct comedi_cmd __user *)arg, file);
+ break;
+ case COMEDI_CMDTEST:
+ rc = do_cmdtest_ioctl(dev, (struct comedi_cmd __user *)arg,
+ file);
+ break;
+ case COMEDI_INSNLIST:
+ rc = do_insnlist_ioctl(dev,
+ (struct comedi_insnlist __user *)arg,
+ file);
+ break;
+ case COMEDI_INSN:
+ rc = do_insn_ioctl(dev, (struct comedi_insn __user *)arg,
+ file);
+ break;
+ case COMEDI_POLL:
+ rc = do_poll_ioctl(dev, arg, file);
+ break;
+ case COMEDI_SETRSUBD:
+ rc = do_setrsubd_ioctl(dev, arg, file);
+ break;
+ case COMEDI_SETWSUBD:
+ rc = do_setwsubd_ioctl(dev, arg, file);
+ break;
+ default:
+ rc = -ENOTTY;
+ break;
+ }
+
+done:
+ mutex_unlock(&dev->mutex);
+ return rc;
+}
+
+static void comedi_vm_open(struct vm_area_struct *area)
+{
+ struct comedi_buf_map *bm;
+
+ bm = area->vm_private_data;
+ comedi_buf_map_get(bm);
+}
+
+static void comedi_vm_close(struct vm_area_struct *area)
+{
+ struct comedi_buf_map *bm;
+
+ bm = area->vm_private_data;
+ comedi_buf_map_put(bm);
+}
+
+static struct vm_operations_struct comedi_vm_ops = {
+ .open = comedi_vm_open,
+ .close = comedi_vm_close,
+};
+
+static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+ struct comedi_buf_map *bm = NULL;
+ unsigned long start = vma->vm_start;
+ unsigned long size;
+ int n_pages;
+ int i;
+ int retval;
+
+ /*
+ * 'trylock' avoids circular dependency with current->mm->mmap_sem
+ * and down-reading &dev->attach_lock should normally succeed without
+ * contention unless the device is in the process of being attached
+ * or detached.
+ */
+ if (!down_read_trylock(&dev->attach_lock))
+ return -EAGAIN;
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ retval = -ENODEV;
+ goto done;
+ }
+
+ if (vma->vm_flags & VM_WRITE)
+ s = comedi_file_write_subdevice(file);
+ else
+ s = comedi_file_read_subdevice(file);
+ if (!s) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ async = s->async;
+ if (!async) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ if (vma->vm_pgoff != 0) {
+ dev_dbg(dev->class_dev, "mmap() offset must be 0.\n");
+ retval = -EINVAL;
+ goto done;
+ }
+
+ size = vma->vm_end - vma->vm_start;
+ if (size > async->prealloc_bufsz) {
+ retval = -EFAULT;
+ goto done;
+ }
+ if (size & (~PAGE_MASK)) {
+ retval = -EFAULT;
+ goto done;
+ }
+
+ n_pages = size >> PAGE_SHIFT;
+
+ /* get reference to current buf map (if any) */
+ bm = comedi_buf_map_from_subdev_get(s);
+ if (!bm || n_pages > bm->n_pages) {
+ retval = -EINVAL;
+ goto done;
+ }
+ for (i = 0; i < n_pages; ++i) {
+ struct comedi_buf_page *buf = &bm->page_list[i];
+
+ if (remap_pfn_range(vma, start,
+ page_to_pfn(virt_to_page(buf->virt_addr)),
+ PAGE_SIZE, PAGE_SHARED)) {
+ retval = -EAGAIN;
+ goto done;
+ }
+ start += PAGE_SIZE;
+ }
+
+ vma->vm_ops = &comedi_vm_ops;
+ vma->vm_private_data = bm;
+
+ vma->vm_ops->open(vma);
+
+ retval = 0;
+done:
+ up_read(&dev->attach_lock);
+ comedi_buf_map_put(bm); /* put reference to buf map - okay if NULL */
+ return retval;
+}
+
+static unsigned int comedi_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ struct comedi_subdevice *s;
+
+ mutex_lock(&dev->mutex);
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ goto done;
+ }
+
+ s = comedi_file_read_subdevice(file);
+ if (s && s->async) {
+ poll_wait(file, &s->async->wait_head, wait);
+ if (!s->busy || !comedi_is_subdevice_running(s) ||
+ (s->async->cmd.flags & CMDF_WRITE) ||
+ comedi_buf_read_n_available(s) > 0)
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ s = comedi_file_write_subdevice(file);
+ if (s && s->async) {
+ unsigned int bps = comedi_bytes_per_sample(s);
+
+ poll_wait(file, &s->async->wait_head, wait);
+ comedi_buf_write_alloc(s, s->async->prealloc_bufsz);
+ if (!s->busy || !comedi_is_subdevice_running(s) ||
+ !(s->async->cmd.flags & CMDF_WRITE) ||
+ comedi_buf_write_n_allocated(s) >= bps)
+ mask |= POLLOUT | POLLWRNORM;
+ }
+
+done:
+ mutex_unlock(&dev->mutex);
+ return mask;
+}
+
+static ssize_t comedi_write(struct file *file, const char __user *buf,
+ size_t nbytes, loff_t *offset)
+{
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+ int n, m, count = 0, retval = 0;
+ DECLARE_WAITQUEUE(wait, current);
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ bool on_wait_queue = false;
+ bool attach_locked;
+ unsigned int old_detach_count;
+
+ /* Protect against device detachment during operation. */
+ down_read(&dev->attach_lock);
+ attach_locked = true;
+ old_detach_count = dev->detach_count;
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ retval = -ENODEV;
+ goto out;
+ }
+
+ s = comedi_file_write_subdevice(file);
+ if (!s || !s->async) {
+ retval = -EIO;
+ goto out;
+ }
+
+ async = s->async;
+
+ if (!s->busy || !nbytes)
+ goto out;
+ if (s->busy != file) {
+ retval = -EACCES;
+ goto out;
+ }
+ if (!(async->cmd.flags & CMDF_WRITE)) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ add_wait_queue(&async->wait_head, &wait);
+ on_wait_queue = true;
+ while (nbytes > 0 && !retval) {
+ unsigned runflags;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ runflags = comedi_get_subdevice_runflags(s);
+ if (!comedi_is_runflags_running(runflags)) {
+ if (count == 0) {
+ struct comedi_subdevice *new_s;
+
+ if (comedi_is_runflags_in_error(runflags))
+ retval = -EPIPE;
+ else
+ retval = 0;
+ /*
+ * To avoid deadlock, cannot acquire dev->mutex
+ * while dev->attach_lock is held. Need to
+ * remove task from the async wait queue before
+ * releasing dev->attach_lock, as it might not
+ * be valid afterwards.
+ */
+ remove_wait_queue(&async->wait_head, &wait);
+ on_wait_queue = false;
+ up_read(&dev->attach_lock);
+ attach_locked = false;
+ mutex_lock(&dev->mutex);
+ /*
+ * Become non-busy unless things have changed
+ * behind our back. Checking dev->detach_count
+ * is unchanged ought to be sufficient (unless
+ * there have been 2**32 detaches in the
+ * meantime!), but check the subdevice pointer
+ * as well just in case.
+ */
+ new_s = comedi_file_write_subdevice(file);
+ if (dev->attached &&
+ old_detach_count == dev->detach_count &&
+ s == new_s && new_s->async == async)
+ do_become_nonbusy(dev, s);
+ mutex_unlock(&dev->mutex);
+ }
+ break;
+ }
+
+ n = nbytes;
+
+ m = n;
+ if (async->buf_write_ptr + m > async->prealloc_bufsz)
+ m = async->prealloc_bufsz - async->buf_write_ptr;
+ comedi_buf_write_alloc(s, async->prealloc_bufsz);
+ if (m > comedi_buf_write_n_allocated(s))
+ m = comedi_buf_write_n_allocated(s);
+ if (m < n)
+ n = m;
+
+ if (n == 0) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ break;
+ }
+ schedule();
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!s->busy)
+ break;
+ if (s->busy != file) {
+ retval = -EACCES;
+ break;
+ }
+ if (!(async->cmd.flags & CMDF_WRITE)) {
+ retval = -EINVAL;
+ break;
+ }
+ continue;
+ }
+
+ m = copy_from_user(async->prealloc_buf + async->buf_write_ptr,
+ buf, n);
+ if (m) {
+ n -= m;
+ retval = -EFAULT;
+ }
+ comedi_buf_write_free(s, n);
+
+ count += n;
+ nbytes -= n;
+
+ buf += n;
+ break; /* makes device work like a pipe */
+ }
+out:
+ if (on_wait_queue)
+ remove_wait_queue(&async->wait_head, &wait);
+ set_current_state(TASK_RUNNING);
+ if (attach_locked)
+ up_read(&dev->attach_lock);
+
+ return count ? count : retval;
+}
+
+static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
+ loff_t *offset)
+{
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+ int n, m, count = 0, retval = 0;
+ DECLARE_WAITQUEUE(wait, current);
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ unsigned int old_detach_count;
+ bool become_nonbusy = false;
+ bool attach_locked;
+
+ /* Protect against device detachment during operation. */
+ down_read(&dev->attach_lock);
+ attach_locked = true;
+ old_detach_count = dev->detach_count;
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ retval = -ENODEV;
+ goto out;
+ }
+
+ s = comedi_file_read_subdevice(file);
+ if (!s || !s->async) {
+ retval = -EIO;
+ goto out;
+ }
+
+ async = s->async;
+ if (!s->busy || !nbytes)
+ goto out;
+ if (s->busy != file) {
+ retval = -EACCES;
+ goto out;
+ }
+ if (async->cmd.flags & CMDF_WRITE) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ add_wait_queue(&async->wait_head, &wait);
+ while (nbytes > 0 && !retval) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ n = nbytes;
+
+ m = comedi_buf_read_n_available(s);
+ if (async->buf_read_ptr + m > async->prealloc_bufsz)
+ m = async->prealloc_bufsz - async->buf_read_ptr;
+ if (m < n)
+ n = m;
+
+ if (n == 0) {
+ unsigned runflags = comedi_get_subdevice_runflags(s);
+
+ if (!comedi_is_runflags_running(runflags)) {
+ if (comedi_is_runflags_in_error(runflags))
+ retval = -EPIPE;
+ else
+ retval = 0;
+ become_nonbusy = true;
+ break;
+ }
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ break;
+ }
+ schedule();
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!s->busy) {
+ retval = 0;
+ break;
+ }
+ if (s->busy != file) {
+ retval = -EACCES;
+ break;
+ }
+ if (async->cmd.flags & CMDF_WRITE) {
+ retval = -EINVAL;
+ break;
+ }
+ continue;
+ }
+ m = copy_to_user(buf, async->prealloc_buf +
+ async->buf_read_ptr, n);
+ if (m) {
+ n -= m;
+ retval = -EFAULT;
+ }
+
+ comedi_buf_read_alloc(s, n);
+ comedi_buf_read_free(s, n);
+
+ count += n;
+ nbytes -= n;
+
+ buf += n;
+ break; /* makes device work like a pipe */
+ }
+ remove_wait_queue(&async->wait_head, &wait);
+ set_current_state(TASK_RUNNING);
+ if (become_nonbusy || comedi_is_subdevice_idle(s)) {
+ struct comedi_subdevice *new_s;
+
+ /*
+ * To avoid deadlock, cannot acquire dev->mutex
+ * while dev->attach_lock is held.
+ */
+ up_read(&dev->attach_lock);
+ attach_locked = false;
+ mutex_lock(&dev->mutex);
+ /*
+ * Check device hasn't become detached behind our back.
+ * Checking dev->detach_count is unchanged ought to be
+ * sufficient (unless there have been 2**32 detaches in the
+ * meantime!), but check the subdevice pointer as well just in
+ * case.
+ */
+ new_s = comedi_file_read_subdevice(file);
+ if (dev->attached && old_detach_count == dev->detach_count &&
+ s == new_s && new_s->async == async) {
+ if (become_nonbusy || comedi_buf_n_bytes_ready(s) == 0)
+ do_become_nonbusy(dev, s);
+ }
+ mutex_unlock(&dev->mutex);
+ }
+out:
+ if (attach_locked)
+ up_read(&dev->attach_lock);
+
+ return count ? count : retval;
+}
+
+static int comedi_open(struct inode *inode, struct file *file)
+{
+ const unsigned minor = iminor(inode);
+ struct comedi_file *cfp;
+ struct comedi_device *dev = comedi_dev_get_from_minor(minor);
+ int rc;
+
+ if (!dev) {
+ pr_debug("invalid minor number\n");
+ return -ENODEV;
+ }
+
+ cfp = kzalloc(sizeof(*cfp), GFP_KERNEL);
+ if (!cfp)
+ return -ENOMEM;
+
+ cfp->dev = dev;
+
+ mutex_lock(&dev->mutex);
+ if (!dev->attached && !capable(CAP_NET_ADMIN)) {
+ dev_dbg(dev->class_dev, "not attached and not CAP_NET_ADMIN\n");
+ rc = -ENODEV;
+ goto out;
+ }
+ if (dev->attached && dev->use_count == 0) {
+ if (!try_module_get(dev->driver->module)) {
+ rc = -ENOSYS;
+ goto out;
+ }
+ if (dev->open) {
+ rc = dev->open(dev);
+ if (rc < 0) {
+ module_put(dev->driver->module);
+ goto out;
+ }
+ }
+ }
+
+ dev->use_count++;
+ file->private_data = cfp;
+ comedi_file_reset(file);
+ rc = 0;
+
+out:
+ mutex_unlock(&dev->mutex);
+ if (rc) {
+ comedi_dev_put(dev);
+ kfree(cfp);
+ }
+ return rc;
+}
+
+static int comedi_fasync(int fd, struct file *file, int on)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+
+ return fasync_helper(fd, file, on, &dev->async_queue);
+}
+
+static int comedi_close(struct inode *inode, struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ struct comedi_subdevice *s = NULL;
+ int i;
+
+ mutex_lock(&dev->mutex);
+
+ if (dev->subdevices) {
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+
+ if (s->busy == file)
+ do_cancel(dev, s);
+ if (s->lock == file)
+ s->lock = NULL;
+ }
+ }
+ if (dev->attached && dev->use_count == 1) {
+ if (dev->close)
+ dev->close(dev);
+ module_put(dev->driver->module);
+ }
+
+ dev->use_count--;
+
+ mutex_unlock(&dev->mutex);
+ comedi_dev_put(dev);
+ kfree(cfp);
+
+ return 0;
+}
+
+static const struct file_operations comedi_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = comedi_unlocked_ioctl,
+ .compat_ioctl = comedi_compat_ioctl,
+ .open = comedi_open,
+ .release = comedi_close,
+ .read = comedi_read,
+ .write = comedi_write,
+ .mmap = comedi_mmap,
+ .poll = comedi_poll,
+ .fasync = comedi_fasync,
+ .llseek = noop_llseek,
+};
+
+/**
+ * comedi_event - handle events for asynchronous comedi command
+ * @dev: comedi_device struct
+ * @s: comedi_subdevice struct associated with dev
+ * Context: interrupt (usually), s->spin_lock spin-lock not held
+ *
+ * If an asynchronous comedi command is active on the subdevice, process
+ * any COMEDI_CB_... event flags that have been set, usually by an
+ * interrupt handler. These may change the run state of the asynchronous
+ * command, wake a task, and/or send a SIGIO signal.
+ */
+void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ unsigned int events;
+ int si_code = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+
+ events = async->events;
+ async->events = 0;
+ if (!__comedi_is_subdevice_running(s)) {
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+ return;
+ }
+
+ if (events & COMEDI_CB_CANCEL_MASK)
+ __comedi_clear_subdevice_runflags(s, COMEDI_SRF_RUNNING);
+
+ /*
+ * Remember if an error event has occurred, so an error can be
+ * returned the next time the user does a read() or write().
+ */
+ if (events & COMEDI_CB_ERROR_MASK)
+ __comedi_set_subdevice_runflags(s, COMEDI_SRF_ERROR);
+
+ if (async->cb_mask & events) {
+ wake_up_interruptible(&async->wait_head);
+ si_code = async->cmd.flags & CMDF_WRITE ? POLL_OUT : POLL_IN;
+ }
+
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+
+ if (si_code)
+ kill_fasync(&dev->async_queue, SIGIO, si_code);
+}
+EXPORT_SYMBOL_GPL(comedi_event);
+
+/* Note: the ->mutex is pre-locked on successful return */
+struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device)
+{
+ struct comedi_device *dev;
+ struct device *csdev;
+ unsigned i;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return ERR_PTR(-ENOMEM);
+ comedi_device_init(dev);
+ comedi_set_hw_dev(dev, hardware_device);
+ mutex_lock(&dev->mutex);
+ mutex_lock(&comedi_board_minor_table_lock);
+ for (i = hardware_device ? comedi_num_legacy_minors : 0;
+ i < COMEDI_NUM_BOARD_MINORS; ++i) {
+ if (!comedi_board_minor_table[i]) {
+ comedi_board_minor_table[i] = dev;
+ break;
+ }
+ }
+ mutex_unlock(&comedi_board_minor_table_lock);
+ if (i == COMEDI_NUM_BOARD_MINORS) {
+ mutex_unlock(&dev->mutex);
+ comedi_device_cleanup(dev);
+ comedi_dev_put(dev);
+ dev_err(hardware_device,
+ "ran out of minor numbers for board device files\n");
+ return ERR_PTR(-EBUSY);
+ }
+ dev->minor = i;
+ csdev = device_create(comedi_class, hardware_device,
+ MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i", i);
+ if (!IS_ERR(csdev))
+ dev->class_dev = get_device(csdev);
+
+ /* Note: dev->mutex needs to be unlocked by the caller. */
+ return dev;
+}
+
+static void comedi_free_board_minor(unsigned minor)
+{
+ BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS);
+ comedi_free_board_dev(comedi_clear_board_minor(minor));
+}
+
+void comedi_release_hardware_device(struct device *hardware_device)
+{
+ int minor;
+ struct comedi_device *dev;
+
+ for (minor = comedi_num_legacy_minors; minor < COMEDI_NUM_BOARD_MINORS;
+ minor++) {
+ mutex_lock(&comedi_board_minor_table_lock);
+ dev = comedi_board_minor_table[minor];
+ if (dev && dev->hw_dev == hardware_device) {
+ comedi_board_minor_table[minor] = NULL;
+ mutex_unlock(&comedi_board_minor_table_lock);
+ comedi_free_board_dev(dev);
+ break;
+ }
+ mutex_unlock(&comedi_board_minor_table_lock);
+ }
+}
+
+int comedi_alloc_subdevice_minor(struct comedi_subdevice *s)
+{
+ struct comedi_device *dev = s->device;
+ struct device *csdev;
+ unsigned i;
+
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i) {
+ if (!comedi_subdevice_minor_table[i]) {
+ comedi_subdevice_minor_table[i] = s;
+ break;
+ }
+ }
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ if (i == COMEDI_NUM_SUBDEVICE_MINORS) {
+ dev_err(dev->class_dev,
+ "ran out of minor numbers for subdevice files\n");
+ return -EBUSY;
+ }
+ i += COMEDI_NUM_BOARD_MINORS;
+ s->minor = i;
+ csdev = device_create(comedi_class, dev->class_dev,
+ MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i_subd%i",
+ dev->minor, s->index);
+ if (!IS_ERR(csdev))
+ s->class_dev = csdev;
+
+ return 0;
+}
+
+void comedi_free_subdevice_minor(struct comedi_subdevice *s)
+{
+ unsigned int i;
+
+ if (!s)
+ return;
+ if (s->minor < 0)
+ return;
+
+ BUG_ON(s->minor >= COMEDI_NUM_MINORS);
+ BUG_ON(s->minor < COMEDI_NUM_BOARD_MINORS);
+
+ i = s->minor - COMEDI_NUM_BOARD_MINORS;
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ if (s == comedi_subdevice_minor_table[i])
+ comedi_subdevice_minor_table[i] = NULL;
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ if (s->class_dev) {
+ device_destroy(comedi_class, MKDEV(COMEDI_MAJOR, s->minor));
+ s->class_dev = NULL;
+ }
+}
+
+static void comedi_cleanup_board_minors(void)
+{
+ unsigned i;
+
+ for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++)
+ comedi_free_board_minor(i);
+}
+
+static int __init comedi_init(void)
+{
+ int i;
+ int retval;
+
+ pr_info("version " COMEDI_RELEASE " - http://www.comedi.org\n");
+
+ if (comedi_num_legacy_minors < 0 ||
+ comedi_num_legacy_minors > COMEDI_NUM_BOARD_MINORS) {
+ pr_err("invalid value for module parameter \"comedi_num_legacy_minors\". Valid values are 0 through %i.\n",
+ COMEDI_NUM_BOARD_MINORS);
+ return -EINVAL;
+ }
+
+ retval = register_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
+ COMEDI_NUM_MINORS, "comedi");
+ if (retval)
+ return -EIO;
+ cdev_init(&comedi_cdev, &comedi_fops);
+ comedi_cdev.owner = THIS_MODULE;
+
+ retval = kobject_set_name(&comedi_cdev.kobj, "comedi");
+ if (retval) {
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
+ COMEDI_NUM_MINORS);
+ return retval;
+ }
+
+ if (cdev_add(&comedi_cdev, MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS)) {
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
+ COMEDI_NUM_MINORS);
+ return -EIO;
+ }
+ comedi_class = class_create(THIS_MODULE, "comedi");
+ if (IS_ERR(comedi_class)) {
+ pr_err("failed to create class\n");
+ cdev_del(&comedi_cdev);
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
+ COMEDI_NUM_MINORS);
+ return PTR_ERR(comedi_class);
+ }
+
+ comedi_class->dev_groups = comedi_dev_groups;
+
+ /* XXX requires /proc interface */
+ comedi_proc_init();
+
+ /* create devices files for legacy/manual use */
+ for (i = 0; i < comedi_num_legacy_minors; i++) {
+ struct comedi_device *dev;
+
+ dev = comedi_alloc_board_minor(NULL);
+ if (IS_ERR(dev)) {
+ comedi_cleanup_board_minors();
+ cdev_del(&comedi_cdev);
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
+ COMEDI_NUM_MINORS);
+ return PTR_ERR(dev);
+ }
+ /* comedi_alloc_board_minor() locked the mutex */
+ mutex_unlock(&dev->mutex);
+ }
+
+ return 0;
+}
+module_init(comedi_init);
+
+static void __exit comedi_cleanup(void)
+{
+ int i;
+
+ comedi_cleanup_board_minors();
+ for (i = 0; i < COMEDI_NUM_BOARD_MINORS; ++i)
+ BUG_ON(comedi_board_minor_table[i]);
+ for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i)
+ BUG_ON(comedi_subdevice_minor_table[i]);
+
+ class_destroy(comedi_class);
+ cdev_del(&comedi_cdev);
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS);
+
+ comedi_proc_cleanup();
+}
+module_exit(comedi_cleanup);
+
+MODULE_AUTHOR("http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi core module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/comedi_internal.h b/drivers/staging/comedi/comedi_internal.h
new file mode 100644
index 000000000..3b9185388
--- /dev/null
+++ b/drivers/staging/comedi/comedi_internal.h
@@ -0,0 +1,67 @@
+#ifndef _COMEDI_INTERNAL_H
+#define _COMEDI_INTERNAL_H
+
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+/*
+ * various internal comedi stuff
+ */
+
+struct comedi_buf_map;
+struct comedi_devconfig;
+struct comedi_device;
+struct comedi_insn;
+struct comedi_rangeinfo;
+struct comedi_subdevice;
+struct device;
+
+int do_rangeinfo_ioctl(struct comedi_device *dev,
+ struct comedi_rangeinfo __user *arg);
+struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device);
+void comedi_release_hardware_device(struct device *hardware_device);
+int comedi_alloc_subdevice_minor(struct comedi_subdevice *s);
+void comedi_free_subdevice_minor(struct comedi_subdevice *s);
+
+int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s,
+ unsigned long new_size);
+void comedi_buf_reset(struct comedi_subdevice *s);
+bool comedi_buf_is_mmapped(struct comedi_subdevice *s);
+void comedi_buf_map_get(struct comedi_buf_map *bm);
+int comedi_buf_map_put(struct comedi_buf_map *bm);
+struct comedi_buf_map *comedi_buf_map_from_subdev_get(
+ struct comedi_subdevice *s);
+unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s);
+void comedi_device_cancel_all(struct comedi_device *dev);
+
+extern unsigned int comedi_default_buf_size_kb;
+extern unsigned int comedi_default_buf_maxsize_kb;
+
+/* drivers.c */
+
+extern struct comedi_driver *comedi_drivers;
+extern struct mutex comedi_drivers_list_lock;
+
+int insn_inval(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+
+void comedi_device_detach(struct comedi_device *);
+int comedi_device_attach(struct comedi_device *, struct comedi_devconfig *);
+
+#ifdef CONFIG_PROC_FS
+
+/* proc.c */
+
+void comedi_proc_init(void);
+void comedi_proc_cleanup(void);
+#else
+static inline void comedi_proc_init(void)
+{
+}
+
+static inline void comedi_proc_cleanup(void)
+{
+}
+#endif
+
+#endif /* _COMEDI_INTERNAL_H */
diff --git a/drivers/staging/comedi/comedi_pci.c b/drivers/staging/comedi/comedi_pci.c
new file mode 100644
index 000000000..027f0f4e5
--- /dev/null
+++ b/drivers/staging/comedi/comedi_pci.c
@@ -0,0 +1,185 @@
+/*
+ * comedi_pci.c
+ * Comedi PCI driver specific functions.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "comedi_pci.h"
+
+/**
+ * comedi_to_pci_dev() - comedi_device pointer to pci_dev pointer.
+ * @dev: comedi_device struct
+ */
+struct pci_dev *comedi_to_pci_dev(struct comedi_device *dev)
+{
+ return dev->hw_dev ? to_pci_dev(dev->hw_dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_pci_dev);
+
+/**
+ * comedi_pci_enable() - Enable the PCI device and request the regions.
+ * @dev: comedi_device struct
+ */
+int comedi_pci_enable(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ int rc;
+
+ if (!pcidev)
+ return -ENODEV;
+
+ rc = pci_enable_device(pcidev);
+ if (rc < 0)
+ return rc;
+
+ rc = pci_request_regions(pcidev, dev->board_name);
+ if (rc < 0)
+ pci_disable_device(pcidev);
+ else
+ dev->ioenabled = true;
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(comedi_pci_enable);
+
+/**
+ * comedi_pci_disable() - Release the regions and disable the PCI device.
+ * @dev: comedi_device struct
+ */
+void comedi_pci_disable(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+ if (pcidev && dev->ioenabled) {
+ pci_release_regions(pcidev);
+ pci_disable_device(pcidev);
+ }
+ dev->ioenabled = false;
+}
+EXPORT_SYMBOL_GPL(comedi_pci_disable);
+
+/**
+ * comedi_pci_detach() - A generic (*detach) function for PCI drivers.
+ * @dev: comedi_device struct
+ */
+void comedi_pci_detach(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+ if (!pcidev || !dev->ioenabled)
+ return;
+
+ if (dev->irq) {
+ free_irq(dev->irq, dev);
+ dev->irq = 0;
+ }
+ if (dev->mmio) {
+ iounmap(dev->mmio);
+ dev->mmio = NULL;
+ }
+ comedi_pci_disable(dev);
+}
+EXPORT_SYMBOL_GPL(comedi_pci_detach);
+
+/**
+ * comedi_pci_auto_config() - Configure/probe a comedi PCI driver.
+ * @pcidev: pci_dev struct
+ * @driver: comedi_driver struct
+ * @context: driver specific data, passed to comedi_auto_config()
+ *
+ * Typically called from the pci_driver (*probe) function.
+ */
+int comedi_pci_auto_config(struct pci_dev *pcidev,
+ struct comedi_driver *driver,
+ unsigned long context)
+{
+ return comedi_auto_config(&pcidev->dev, driver, context);
+}
+EXPORT_SYMBOL_GPL(comedi_pci_auto_config);
+
+/**
+ * comedi_pci_auto_unconfig() - Unconfigure/remove a comedi PCI driver.
+ * @pcidev: pci_dev struct
+ *
+ * Typically called from the pci_driver (*remove) function.
+ */
+void comedi_pci_auto_unconfig(struct pci_dev *pcidev)
+{
+ comedi_auto_unconfig(&pcidev->dev);
+}
+EXPORT_SYMBOL_GPL(comedi_pci_auto_unconfig);
+
+/**
+ * comedi_pci_driver_register() - Register a comedi PCI driver.
+ * @comedi_driver: comedi_driver struct
+ * @pci_driver: pci_driver struct
+ *
+ * This function is used for the module_init() of comedi PCI drivers.
+ * Do not call it directly, use the module_comedi_pci_driver() helper
+ * macro instead.
+ */
+int comedi_pci_driver_register(struct comedi_driver *comedi_driver,
+ struct pci_driver *pci_driver)
+{
+ int ret;
+
+ ret = comedi_driver_register(comedi_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = pci_register_driver(pci_driver);
+ if (ret < 0) {
+ comedi_driver_unregister(comedi_driver);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_pci_driver_register);
+
+/**
+ * comedi_pci_driver_unregister() - Unregister a comedi PCI driver.
+ * @comedi_driver: comedi_driver struct
+ * @pci_driver: pci_driver struct
+ *
+ * This function is used for the module_exit() of comedi PCI drivers.
+ * Do not call it directly, use the module_comedi_pci_driver() helper
+ * macro instead.
+ */
+void comedi_pci_driver_unregister(struct comedi_driver *comedi_driver,
+ struct pci_driver *pci_driver)
+{
+ pci_unregister_driver(pci_driver);
+ comedi_driver_unregister(comedi_driver);
+}
+EXPORT_SYMBOL_GPL(comedi_pci_driver_unregister);
+
+static int __init comedi_pci_init(void)
+{
+ return 0;
+}
+module_init(comedi_pci_init);
+
+static void __exit comedi_pci_exit(void)
+{
+}
+module_exit(comedi_pci_exit);
+
+MODULE_AUTHOR("http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi PCI interface module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/comedi_pci.h b/drivers/staging/comedi/comedi_pci.h
new file mode 100644
index 000000000..4005cc9cf
--- /dev/null
+++ b/drivers/staging/comedi/comedi_pci.h
@@ -0,0 +1,64 @@
+/*
+ * comedi_pci.h
+ * header file for Comedi PCI drivers
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _COMEDI_PCI_H
+#define _COMEDI_PCI_H
+
+#include <linux/pci.h>
+
+#include "comedidev.h"
+
+/*
+ * PCI Vendor IDs not in <linux/pci_ids.h>
+ */
+#define PCI_VENDOR_ID_KOLTER 0x1001
+#define PCI_VENDOR_ID_ICP 0x104c
+#define PCI_VENDOR_ID_DT 0x1116
+#define PCI_VENDOR_ID_IOTECH 0x1616
+#define PCI_VENDOR_ID_CONTEC 0x1221
+#define PCI_VENDOR_ID_RTD 0x1435
+#define PCI_VENDOR_ID_HUMUSOFT 0x186c
+
+struct pci_dev *comedi_to_pci_dev(struct comedi_device *);
+
+int comedi_pci_enable(struct comedi_device *);
+void comedi_pci_disable(struct comedi_device *);
+void comedi_pci_detach(struct comedi_device *);
+
+int comedi_pci_auto_config(struct pci_dev *, struct comedi_driver *,
+ unsigned long context);
+void comedi_pci_auto_unconfig(struct pci_dev *);
+
+int comedi_pci_driver_register(struct comedi_driver *, struct pci_driver *);
+void comedi_pci_driver_unregister(struct comedi_driver *, struct pci_driver *);
+
+/**
+ * module_comedi_pci_driver() - Helper macro for registering a comedi PCI driver
+ * @__comedi_driver: comedi_driver struct
+ * @__pci_driver: pci_driver struct
+ *
+ * Helper macro for comedi PCI drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_pci_driver(__comedi_driver, __pci_driver) \
+ module_driver(__comedi_driver, comedi_pci_driver_register, \
+ comedi_pci_driver_unregister, &(__pci_driver))
+
+#endif /* _COMEDI_PCI_H */
diff --git a/drivers/staging/comedi/comedi_pcmcia.c b/drivers/staging/comedi/comedi_pcmcia.c
new file mode 100644
index 000000000..7e784399a
--- /dev/null
+++ b/drivers/staging/comedi/comedi_pcmcia.c
@@ -0,0 +1,169 @@
+/*
+ * comedi_pcmcia.c
+ * Comedi PCMCIA driver specific functions.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include "comedi_pcmcia.h"
+
+/**
+ * comedi_to_pcmcia_dev() - comedi_device pointer to pcmcia_device pointer.
+ * @dev: comedi_device struct
+ */
+struct pcmcia_device *comedi_to_pcmcia_dev(struct comedi_device *dev)
+{
+ return dev->hw_dev ? to_pcmcia_dev(dev->hw_dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_pcmcia_dev);
+
+static int comedi_pcmcia_conf_check(struct pcmcia_device *link,
+ void *priv_data)
+{
+ if (link->config_index == 0)
+ return -EINVAL;
+
+ return pcmcia_request_io(link);
+}
+
+/**
+ * comedi_pcmcia_enable() - Request the regions and enable the PCMCIA device.
+ * @dev: comedi_device struct
+ * @conf_check: optional callback to check the pcmcia_device configuration
+ *
+ * The comedi PCMCIA driver needs to set the link->config_flags, as
+ * appropriate for that driver, before calling this function in order
+ * to allow pcmcia_loop_config() to do its internal autoconfiguration.
+ */
+int comedi_pcmcia_enable(struct comedi_device *dev,
+ int (*conf_check)(struct pcmcia_device *, void *))
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ int ret;
+
+ if (!link)
+ return -ENODEV;
+
+ if (!conf_check)
+ conf_check = comedi_pcmcia_conf_check;
+
+ ret = pcmcia_loop_config(link, conf_check, NULL);
+ if (ret)
+ return ret;
+
+ return pcmcia_enable_device(link);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_enable);
+
+/**
+ * comedi_pcmcia_disable() - Disable the PCMCIA device and release the regions.
+ * @dev: comedi_device struct
+ */
+void comedi_pcmcia_disable(struct comedi_device *dev)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+
+ if (link)
+ pcmcia_disable_device(link);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_disable);
+
+/**
+ * comedi_pcmcia_auto_config() - Configure/probe a comedi PCMCIA driver.
+ * @link: pcmcia_device struct
+ * @driver: comedi_driver struct
+ *
+ * Typically called from the pcmcia_driver (*probe) function.
+ */
+int comedi_pcmcia_auto_config(struct pcmcia_device *link,
+ struct comedi_driver *driver)
+{
+ return comedi_auto_config(&link->dev, driver, 0);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_auto_config);
+
+/**
+ * comedi_pcmcia_auto_unconfig() - Unconfigure/remove a comedi PCMCIA driver.
+ * @link: pcmcia_device struct
+ *
+ * Typically called from the pcmcia_driver (*remove) function.
+ */
+void comedi_pcmcia_auto_unconfig(struct pcmcia_device *link)
+{
+ comedi_auto_unconfig(&link->dev);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_auto_unconfig);
+
+/**
+ * comedi_pcmcia_driver_register() - Register a comedi PCMCIA driver.
+ * @comedi_driver: comedi_driver struct
+ * @pcmcia_driver: pcmcia_driver struct
+ *
+ * This function is used for the module_init() of comedi USB drivers.
+ * Do not call it directly, use the module_comedi_pcmcia_driver() helper
+ * macro instead.
+ */
+int comedi_pcmcia_driver_register(struct comedi_driver *comedi_driver,
+ struct pcmcia_driver *pcmcia_driver)
+{
+ int ret;
+
+ ret = comedi_driver_register(comedi_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = pcmcia_register_driver(pcmcia_driver);
+ if (ret < 0) {
+ comedi_driver_unregister(comedi_driver);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_register);
+
+/**
+ * comedi_pcmcia_driver_unregister() - Unregister a comedi PCMCIA driver.
+ * @comedi_driver: comedi_driver struct
+ * @pcmcia_driver: pcmcia_driver struct
+ *
+ * This function is used for the module_exit() of comedi PCMCIA drivers.
+ * Do not call it directly, use the module_comedi_pcmcia_driver() helper
+ * macro instead.
+ */
+void comedi_pcmcia_driver_unregister(struct comedi_driver *comedi_driver,
+ struct pcmcia_driver *pcmcia_driver)
+{
+ pcmcia_unregister_driver(pcmcia_driver);
+ comedi_driver_unregister(comedi_driver);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_unregister);
+
+static int __init comedi_pcmcia_init(void)
+{
+ return 0;
+}
+module_init(comedi_pcmcia_init);
+
+static void __exit comedi_pcmcia_exit(void)
+{
+}
+module_exit(comedi_pcmcia_exit);
+
+MODULE_AUTHOR("http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi PCMCIA interface module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/comedi_pcmcia.h b/drivers/staging/comedi/comedi_pcmcia.h
new file mode 100644
index 000000000..5d3db2b9b
--- /dev/null
+++ b/drivers/staging/comedi/comedi_pcmcia.h
@@ -0,0 +1,55 @@
+/*
+ * comedi_pcmcia.h
+ * header file for Comedi PCMCIA drivers
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _COMEDI_PCMCIA_H
+#define _COMEDI_PCMCIA_H
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include "comedidev.h"
+
+struct pcmcia_device *comedi_to_pcmcia_dev(struct comedi_device *);
+
+int comedi_pcmcia_enable(struct comedi_device *,
+ int (*conf_check)(struct pcmcia_device *, void *));
+void comedi_pcmcia_disable(struct comedi_device *);
+
+int comedi_pcmcia_auto_config(struct pcmcia_device *, struct comedi_driver *);
+void comedi_pcmcia_auto_unconfig(struct pcmcia_device *);
+
+int comedi_pcmcia_driver_register(struct comedi_driver *,
+ struct pcmcia_driver *);
+void comedi_pcmcia_driver_unregister(struct comedi_driver *,
+ struct pcmcia_driver *);
+
+/**
+ * module_comedi_pcmcia_driver() - Helper macro for registering a comedi PCMCIA driver
+ * @__comedi_driver: comedi_driver struct
+ * @__pcmcia_driver: pcmcia_driver struct
+ *
+ * Helper macro for comedi PCMCIA drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_pcmcia_driver(__comedi_driver, __pcmcia_driver) \
+ module_driver(__comedi_driver, comedi_pcmcia_driver_register, \
+ comedi_pcmcia_driver_unregister, &(__pcmcia_driver))
+
+#endif /* _COMEDI_PCMCIA_H */
diff --git a/drivers/staging/comedi/comedi_usb.c b/drivers/staging/comedi/comedi_usb.c
new file mode 100644
index 000000000..68b75e8fe
--- /dev/null
+++ b/drivers/staging/comedi/comedi_usb.c
@@ -0,0 +1,131 @@
+/*
+ * comedi_usb.c
+ * Comedi USB driver specific functions.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+
+#include "comedi_usb.h"
+
+/**
+ * comedi_to_usb_interface() - comedi_device pointer to usb_interface pointer.
+ * @dev: comedi_device struct
+ */
+struct usb_interface *comedi_to_usb_interface(struct comedi_device *dev)
+{
+ return dev->hw_dev ? to_usb_interface(dev->hw_dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_usb_interface);
+
+/**
+ * comedi_to_usb_dev() - comedi_device pointer to usb_device pointer.
+ * @dev: comedi_device struct
+ */
+struct usb_device *comedi_to_usb_dev(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+
+ return intf ? interface_to_usbdev(intf) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_usb_dev);
+
+/**
+ * comedi_usb_auto_config() - Configure/probe a comedi USB driver.
+ * @intf: usb_interface struct
+ * @driver: comedi_driver struct
+ * @context: driver specific data, passed to comedi_auto_config()
+ *
+ * Typically called from the usb_driver (*probe) function.
+ */
+int comedi_usb_auto_config(struct usb_interface *intf,
+ struct comedi_driver *driver,
+ unsigned long context)
+{
+ return comedi_auto_config(&intf->dev, driver, context);
+}
+EXPORT_SYMBOL_GPL(comedi_usb_auto_config);
+
+/**
+ * comedi_pci_auto_unconfig() - Unconfigure/disconnect a comedi USB driver.
+ * @intf: usb_interface struct
+ *
+ * Typically called from the usb_driver (*disconnect) function.
+ */
+void comedi_usb_auto_unconfig(struct usb_interface *intf)
+{
+ comedi_auto_unconfig(&intf->dev);
+}
+EXPORT_SYMBOL_GPL(comedi_usb_auto_unconfig);
+
+/**
+ * comedi_usb_driver_register() - Register a comedi USB driver.
+ * @comedi_driver: comedi_driver struct
+ * @usb_driver: usb_driver struct
+ *
+ * This function is used for the module_init() of comedi USB drivers.
+ * Do not call it directly, use the module_comedi_usb_driver() helper
+ * macro instead.
+ */
+int comedi_usb_driver_register(struct comedi_driver *comedi_driver,
+ struct usb_driver *usb_driver)
+{
+ int ret;
+
+ ret = comedi_driver_register(comedi_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_register(usb_driver);
+ if (ret < 0) {
+ comedi_driver_unregister(comedi_driver);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_usb_driver_register);
+
+/**
+ * comedi_usb_driver_unregister() - Unregister a comedi USB driver.
+ * @comedi_driver: comedi_driver struct
+ * @usb_driver: usb_driver struct
+ *
+ * This function is used for the module_exit() of comedi USB drivers.
+ * Do not call it directly, use the module_comedi_usb_driver() helper
+ * macro instead.
+ */
+void comedi_usb_driver_unregister(struct comedi_driver *comedi_driver,
+ struct usb_driver *usb_driver)
+{
+ usb_deregister(usb_driver);
+ comedi_driver_unregister(comedi_driver);
+}
+EXPORT_SYMBOL_GPL(comedi_usb_driver_unregister);
+
+static int __init comedi_usb_init(void)
+{
+ return 0;
+}
+module_init(comedi_usb_init);
+
+static void __exit comedi_usb_exit(void)
+{
+}
+module_exit(comedi_usb_exit);
+
+MODULE_AUTHOR("http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi USB interface module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/comedi_usb.h b/drivers/staging/comedi/comedi_usb.h
new file mode 100644
index 000000000..721128bec
--- /dev/null
+++ b/drivers/staging/comedi/comedi_usb.h
@@ -0,0 +1,50 @@
+/*
+ * comedi_usb.h
+ * header file for USB Comedi drivers
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _COMEDI_USB_H
+#define _COMEDI_USB_H
+
+#include <linux/usb.h>
+
+#include "comedidev.h"
+
+struct usb_interface *comedi_to_usb_interface(struct comedi_device *);
+struct usb_device *comedi_to_usb_dev(struct comedi_device *);
+
+int comedi_usb_auto_config(struct usb_interface *, struct comedi_driver *,
+ unsigned long context);
+void comedi_usb_auto_unconfig(struct usb_interface *);
+
+int comedi_usb_driver_register(struct comedi_driver *, struct usb_driver *);
+void comedi_usb_driver_unregister(struct comedi_driver *, struct usb_driver *);
+
+/**
+ * module_comedi_usb_driver() - Helper macro for registering a comedi USB driver
+ * @__comedi_driver: comedi_driver struct
+ * @__usb_driver: usb_driver struct
+ *
+ * Helper macro for comedi USB drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_usb_driver(__comedi_driver, __usb_driver) \
+ module_driver(__comedi_driver, comedi_usb_driver_register, \
+ comedi_usb_driver_unregister, &(__usb_driver))
+
+#endif /* _COMEDI_USB_H */
diff --git a/drivers/staging/comedi/comedidev.h b/drivers/staging/comedi/comedidev.h
new file mode 100644
index 000000000..dfab5a84b
--- /dev/null
+++ b/drivers/staging/comedi/comedidev.h
@@ -0,0 +1,635 @@
+/*
+ include/linux/comedidev.h
+ header file for kernel-only structures, variables, and constants
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _COMEDIDEV_H
+#define _COMEDIDEV_H
+
+#include <linux/dma-mapping.h>
+#include <linux/mutex.h>
+#include <linux/spinlock_types.h>
+#include <linux/rwsem.h>
+#include <linux/kref.h>
+
+#include "comedi.h"
+
+#define COMEDI_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
+#define COMEDI_VERSION_CODE COMEDI_VERSION(COMEDI_MAJORVERSION, \
+ COMEDI_MINORVERSION, COMEDI_MICROVERSION)
+#define COMEDI_RELEASE VERSION
+
+#define COMEDI_NUM_BOARD_MINORS 0x30
+
+struct comedi_subdevice {
+ struct comedi_device *device;
+ int index;
+ int type;
+ int n_chan;
+ int subdev_flags;
+ int len_chanlist; /* maximum length of channel/gain list */
+
+ void *private;
+
+ struct comedi_async *async;
+
+ void *lock;
+ void *busy;
+ unsigned runflags;
+ spinlock_t spin_lock;
+
+ unsigned int io_bits;
+
+ unsigned int maxdata; /* if maxdata==0, use list */
+ const unsigned int *maxdata_list; /* list is channel specific */
+
+ const struct comedi_lrange *range_table;
+ const struct comedi_lrange *const *range_table_list;
+
+ unsigned int *chanlist; /* driver-owned chanlist (not used) */
+
+ int (*insn_read)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*insn_write)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*insn_bits)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*insn_config)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+
+ int (*do_cmd)(struct comedi_device *, struct comedi_subdevice *);
+ int (*do_cmdtest)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_cmd *);
+ int (*poll)(struct comedi_device *, struct comedi_subdevice *);
+ int (*cancel)(struct comedi_device *, struct comedi_subdevice *);
+
+ /* called when the buffer changes */
+ int (*buf_change)(struct comedi_device *, struct comedi_subdevice *);
+
+ void (*munge)(struct comedi_device *dev, struct comedi_subdevice *s,
+ void *data, unsigned int num_bytes,
+ unsigned int start_chan_index);
+ enum dma_data_direction async_dma_dir;
+
+ unsigned int state;
+
+ struct device *class_dev;
+ int minor;
+
+ unsigned int *readback;
+};
+
+struct comedi_buf_page {
+ void *virt_addr;
+ dma_addr_t dma_addr;
+};
+
+struct comedi_buf_map {
+ struct device *dma_hw_dev;
+ struct comedi_buf_page *page_list;
+ unsigned int n_pages;
+ enum dma_data_direction dma_dir;
+ struct kref refcount;
+};
+
+/**
+ * struct comedi_async - control data for asynchronous comedi commands
+ * @prealloc_buf: preallocated buffer
+ * @prealloc_bufsz: buffer size (in bytes)
+ * @buf_map: map of buffer pages
+ * @max_bufsize: maximum buffer size (in bytes)
+ * @buf_write_count: "write completed" count (in bytes, modulo 2**32)
+ * @buf_write_alloc_count: "allocated for writing" count (in bytes,
+ * modulo 2**32)
+ * @buf_read_count: "read completed" count (in bytes, modulo 2**32)
+ * @buf_read_alloc_count: "allocated for reading" count (in bytes,
+ * modulo 2**32)
+ * @buf_write_ptr: buffer position for writer
+ * @buf_read_ptr: buffer position for reader
+ * @cur_chan: current position in chanlist for scan (for those
+ * drivers that use it)
+ * @scans_done: the number of scans completed (COMEDI_CB_EOS)
+ * @scan_progress: amount received or sent for current scan (in bytes)
+ * @munge_chan: current position in chanlist for "munging"
+ * @munge_count: "munge" count (in bytes, modulo 2**32)
+ * @munge_ptr: buffer position for "munging"
+ * @events: bit-vector of events that have occurred
+ * @cmd: details of comedi command in progress
+ * @wait_head: task wait queue for file reader or writer
+ * @cb_mask: bit-vector of events that should wake waiting tasks
+ * @inttrig: software trigger function for command, or NULL
+ *
+ * Note about the ..._count and ..._ptr members:
+ *
+ * Think of the _Count values being integers of unlimited size, indexing
+ * into a buffer of infinite length (though only an advancing portion
+ * of the buffer of fixed length prealloc_bufsz is accessible at any time).
+ * Then:
+ *
+ * Buf_Read_Count <= Buf_Read_Alloc_Count <= Munge_Count <=
+ * Buf_Write_Count <= Buf_Write_Alloc_Count <=
+ * (Buf_Read_Count + prealloc_bufsz)
+ *
+ * (Those aren't the actual members, apart from prealloc_bufsz.) When
+ * the buffer is reset, those _Count values start at 0 and only increase
+ * in value, maintaining the above inequalities until the next time the
+ * buffer is reset. The buffer is divided into the following regions by
+ * the inequalities:
+ *
+ * [0, Buf_Read_Count):
+ * old region no longer accessible
+ * [Buf_Read_Count, Buf_Read_Alloc_Count):
+ * filled and munged region allocated for reading but not yet read
+ * [Buf_Read_Alloc_Count, Munge_Count):
+ * filled and munged region not yet allocated for reading
+ * [Munge_Count, Buf_Write_Count):
+ * filled region not yet munged
+ * [Buf_Write_Count, Buf_Write_Alloc_Count):
+ * unfilled region allocated for writing but not yet written
+ * [Buf_Write_Alloc_Count, Buf_Read_Count + prealloc_bufsz):
+ * unfilled region not yet allocated for writing
+ * [Buf_Read_Count + prealloc_bufsz, infinity):
+ * unfilled region not yet accessible
+ *
+ * Data needs to be written into the buffer before it can be read out,
+ * and may need to be converted (or "munged") between the two
+ * operations. Extra unfilled buffer space may need to allocated for
+ * writing (advancing Buf_Write_Alloc_Count) before new data is written.
+ * After writing new data, the newly filled space needs to be released
+ * (advancing Buf_Write_Count). This also results in the new data being
+ * "munged" (advancing Munge_Count). Before data is read out of the
+ * buffer, extra space may need to be allocated for reading (advancing
+ * Buf_Read_Alloc_Count). After the data has been read out, the space
+ * needs to be released (advancing Buf_Read_Count).
+ *
+ * The actual members, buf_read_count, buf_read_alloc_count,
+ * munge_count, buf_write_count, and buf_write_alloc_count take the
+ * value of the corresponding capitalized _Count values modulo 2^32
+ * (UINT_MAX+1). Subtracting a "higher" _count value from a "lower"
+ * _count value gives the same answer as subtracting a "higher" _Count
+ * value from a lower _Count value because prealloc_bufsz < UINT_MAX+1.
+ * The modulo operation is done implicitly.
+ *
+ * The buf_read_ptr, munge_ptr, and buf_write_ptr members take the value
+ * of the corresponding capitalized _Count values modulo prealloc_bufsz.
+ * These correspond to byte indices in the physical buffer. The modulo
+ * operation is done by subtracting prealloc_bufsz when the value
+ * exceeds prealloc_bufsz (assuming prealloc_bufsz plus the increment is
+ * less than or equal to UINT_MAX).
+ */
+struct comedi_async {
+ void *prealloc_buf;
+ unsigned int prealloc_bufsz;
+ struct comedi_buf_map *buf_map;
+ unsigned int max_bufsize;
+ unsigned int buf_write_count;
+ unsigned int buf_write_alloc_count;
+ unsigned int buf_read_count;
+ unsigned int buf_read_alloc_count;
+ unsigned int buf_write_ptr;
+ unsigned int buf_read_ptr;
+ unsigned int cur_chan;
+ unsigned int scans_done;
+ unsigned int scan_progress;
+ unsigned int munge_chan;
+ unsigned int munge_count;
+ unsigned int munge_ptr;
+ unsigned int events;
+ struct comedi_cmd cmd;
+ wait_queue_head_t wait_head;
+ unsigned int cb_mask;
+ int (*inttrig)(struct comedi_device *dev, struct comedi_subdevice *s,
+ unsigned int x);
+};
+
+/**
+ * comedi_async callback "events"
+ * @COMEDI_CB_EOS: end-of-scan
+ * @COMEDI_CB_EOA: end-of-acquisition/output
+ * @COMEDI_CB_BLOCK: data has arrived, wakes up read() / write()
+ * @COMEDI_CB_EOBUF: DEPRECATED: end of buffer
+ * @COMEDI_CB_ERROR: card error during acquisition
+ * @COMEDI_CB_OVERFLOW: buffer overflow/underflow
+ *
+ * @COMEDI_CB_ERROR_MASK: events that indicate an error has occurred
+ * @COMEDI_CB_CANCEL_MASK: events that will cancel an async command
+ */
+#define COMEDI_CB_EOS (1 << 0)
+#define COMEDI_CB_EOA (1 << 1)
+#define COMEDI_CB_BLOCK (1 << 2)
+#define COMEDI_CB_EOBUF (1 << 3)
+#define COMEDI_CB_ERROR (1 << 4)
+#define COMEDI_CB_OVERFLOW (1 << 5)
+
+#define COMEDI_CB_ERROR_MASK (COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)
+#define COMEDI_CB_CANCEL_MASK (COMEDI_CB_EOA | COMEDI_CB_ERROR_MASK)
+
+struct comedi_driver {
+ struct comedi_driver *next;
+
+ const char *driver_name;
+ struct module *module;
+ int (*attach)(struct comedi_device *, struct comedi_devconfig *);
+ void (*detach)(struct comedi_device *);
+ int (*auto_attach)(struct comedi_device *, unsigned long);
+
+ /* number of elements in board_name and board_id arrays */
+ unsigned int num_names;
+ const char *const *board_name;
+ /* offset in bytes from one board name pointer to the next */
+ int offset;
+};
+
+struct comedi_device {
+ int use_count;
+ struct comedi_driver *driver;
+ struct comedi_8254 *pacer;
+ void *private;
+
+ struct device *class_dev;
+ int minor;
+ unsigned int detach_count;
+ /* hw_dev is passed to dma_alloc_coherent when allocating async buffers
+ * for subdevices that have async_dma_dir set to something other than
+ * DMA_NONE */
+ struct device *hw_dev;
+
+ const char *board_name;
+ const void *board_ptr;
+ bool attached:1;
+ bool ioenabled:1;
+ spinlock_t spinlock;
+ struct mutex mutex;
+ struct rw_semaphore attach_lock;
+ struct kref refcount;
+
+ int n_subdevices;
+ struct comedi_subdevice *subdevices;
+
+ /* dumb */
+ void __iomem *mmio;
+ unsigned long iobase;
+ unsigned long iolen;
+ unsigned int irq;
+
+ struct comedi_subdevice *read_subdev;
+ struct comedi_subdevice *write_subdev;
+
+ struct fasync_struct *async_queue;
+
+ int (*open)(struct comedi_device *dev);
+ void (*close)(struct comedi_device *dev);
+};
+
+/*
+ * function prototypes
+ */
+
+void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s);
+
+struct comedi_device *comedi_dev_get_from_minor(unsigned minor);
+int comedi_dev_put(struct comedi_device *dev);
+
+/**
+ * comedi_subdevice "runflags"
+ * @COMEDI_SRF_RT: DEPRECATED: command is running real-time
+ * @COMEDI_SRF_ERROR: indicates an COMEDI_CB_ERROR event has occurred
+ * since the last command was started
+ * @COMEDI_SRF_RUNNING: command is running
+ * @COMEDI_SRF_FREE_SPRIV: free s->private on detach
+ *
+ * @COMEDI_SRF_BUSY_MASK: runflags that indicate the subdevice is "busy"
+ */
+#define COMEDI_SRF_RT BIT(1)
+#define COMEDI_SRF_ERROR BIT(2)
+#define COMEDI_SRF_RUNNING BIT(27)
+#define COMEDI_SRF_FREE_SPRIV BIT(31)
+
+#define COMEDI_SRF_BUSY_MASK (COMEDI_SRF_ERROR | COMEDI_SRF_RUNNING)
+
+bool comedi_is_subdevice_running(struct comedi_subdevice *s);
+
+void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size);
+
+int comedi_check_chanlist(struct comedi_subdevice *s,
+ int n,
+ unsigned int *chanlist);
+
+/* range stuff */
+
+#define RANGE(a, b) {(a)*1e6, (b)*1e6, 0}
+#define RANGE_ext(a, b) {(a)*1e6, (b)*1e6, RF_EXTERNAL}
+#define RANGE_mA(a, b) {(a)*1e6, (b)*1e6, UNIT_mA}
+#define RANGE_unitless(a, b) {(a)*1e6, (b)*1e6, 0}
+#define BIP_RANGE(a) {-(a)*1e6, (a)*1e6, 0}
+#define UNI_RANGE(a) {0, (a)*1e6, 0}
+
+extern const struct comedi_lrange range_bipolar10;
+extern const struct comedi_lrange range_bipolar5;
+extern const struct comedi_lrange range_bipolar2_5;
+extern const struct comedi_lrange range_unipolar10;
+extern const struct comedi_lrange range_unipolar5;
+extern const struct comedi_lrange range_unipolar2_5;
+extern const struct comedi_lrange range_0_20mA;
+extern const struct comedi_lrange range_4_20mA;
+extern const struct comedi_lrange range_0_32mA;
+extern const struct comedi_lrange range_unknown;
+
+#define range_digital range_unipolar5
+
+#if __GNUC__ >= 3
+#define GCC_ZERO_LENGTH_ARRAY
+#else
+#define GCC_ZERO_LENGTH_ARRAY 0
+#endif
+
+struct comedi_lrange {
+ int length;
+ struct comedi_krange range[GCC_ZERO_LENGTH_ARRAY];
+};
+
+static inline bool comedi_range_is_bipolar(struct comedi_subdevice *s,
+ unsigned int range)
+{
+ return s->range_table->range[range].min < 0;
+}
+
+static inline bool comedi_range_is_unipolar(struct comedi_subdevice *s,
+ unsigned int range)
+{
+ return s->range_table->range[range].min >= 0;
+}
+
+static inline bool comedi_range_is_external(struct comedi_subdevice *s,
+ unsigned int range)
+{
+ return !!(s->range_table->range[range].flags & RF_EXTERNAL);
+}
+
+static inline bool comedi_chan_range_is_bipolar(struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int range)
+{
+ return s->range_table_list[chan]->range[range].min < 0;
+}
+
+static inline bool comedi_chan_range_is_unipolar(struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int range)
+{
+ return s->range_table_list[chan]->range[range].min >= 0;
+}
+
+static inline bool comedi_chan_range_is_external(struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int range)
+{
+ return !!(s->range_table_list[chan]->range[range].flags & RF_EXTERNAL);
+}
+
+/* munge between offset binary and two's complement values */
+static inline unsigned int comedi_offset_munge(struct comedi_subdevice *s,
+ unsigned int val)
+{
+ return val ^ s->maxdata ^ (s->maxdata >> 1);
+}
+
+/**
+ * comedi_bytes_per_sample - determine subdevice sample size
+ * @s: comedi_subdevice struct
+ *
+ * The sample size will be 4 (sizeof int) or 2 (sizeof short) depending on
+ * whether the SDF_LSAMPL subdevice flag is set or not.
+ *
+ * Returns the subdevice sample size.
+ */
+static inline unsigned int comedi_bytes_per_sample(struct comedi_subdevice *s)
+{
+ return s->subdev_flags & SDF_LSAMPL ? sizeof(int) : sizeof(short);
+}
+
+/**
+ * comedi_sample_shift - determine log2 of subdevice sample size
+ * @s: comedi_subdevice struct
+ *
+ * The sample size will be 4 (sizeof int) or 2 (sizeof short) depending on
+ * whether the SDF_LSAMPL subdevice flag is set or not. The log2 of the
+ * sample size will be 2 or 1 and can be used as the right operand of a
+ * bit-shift operator to multiply or divide something by the sample size.
+ *
+ * Returns log2 of the subdevice sample size.
+ */
+static inline unsigned int comedi_sample_shift(struct comedi_subdevice *s)
+{
+ return s->subdev_flags & SDF_LSAMPL ? 2 : 1;
+}
+
+/**
+ * comedi_bytes_to_samples - converts a number of bytes to a number of samples
+ * @s: comedi_subdevice struct
+ * @nbytes: number of bytes
+ *
+ * Returns the number of bytes divided by the subdevice sample size.
+ */
+static inline unsigned int comedi_bytes_to_samples(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ return nbytes >> comedi_sample_shift(s);
+}
+
+/**
+ * comedi_samples_to_bytes - converts a number of samples to a number of bytes
+ * @s: comedi_subdevice struct
+ * @nsamples: number of samples
+ *
+ * Returns the number of samples multiplied by the subdevice sample size.
+ * Does not check for arithmetic overflow.
+ */
+static inline unsigned int comedi_samples_to_bytes(struct comedi_subdevice *s,
+ unsigned int nsamples)
+{
+ return nsamples << comedi_sample_shift(s);
+}
+
+/**
+ * comedi_check_trigger_src() - trivially validate a comedi_cmd trigger source
+ * @src: pointer to the trigger source to validate
+ * @flags: bitmask of valid TRIG_* for the trigger
+ *
+ * This is used in "step 1" of the do_cmdtest functions of comedi drivers
+ * to vaildate the comedi_cmd triggers. The mask of the @src against the
+ * @flags allows the userspace comedilib to pass all the comedi_cmd
+ * triggers as TRIG_ANY and get back a bitmask of the valid trigger sources.
+ */
+static inline int comedi_check_trigger_src(unsigned int *src,
+ unsigned int flags)
+{
+ unsigned int orig_src = *src;
+
+ *src = orig_src & flags;
+ if (*src == TRIG_INVALID || *src != orig_src)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * comedi_check_trigger_is_unique() - make sure a trigger source is unique
+ * @src: the trigger source to check
+ */
+static inline int comedi_check_trigger_is_unique(unsigned int src)
+{
+ /* this test is true if more than one _src bit is set */
+ if ((src & (src - 1)) != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * comedi_check_trigger_arg_is() - trivially validate a trigger argument
+ * @arg: pointer to the trigger arg to validate
+ * @val: the value the argument should be
+ */
+static inline int comedi_check_trigger_arg_is(unsigned int *arg,
+ unsigned int val)
+{
+ if (*arg != val) {
+ *arg = val;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * comedi_check_trigger_arg_min() - trivially validate a trigger argument
+ * @arg: pointer to the trigger arg to validate
+ * @val: the minimum value the argument should be
+ */
+static inline int comedi_check_trigger_arg_min(unsigned int *arg,
+ unsigned int val)
+{
+ if (*arg < val) {
+ *arg = val;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * comedi_check_trigger_arg_max() - trivially validate a trigger argument
+ * @arg: pointer to the trigger arg to validate
+ * @val: the maximum value the argument should be
+ */
+static inline int comedi_check_trigger_arg_max(unsigned int *arg,
+ unsigned int val)
+{
+ if (*arg > val) {
+ *arg = val;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Must set dev->hw_dev if you wish to dma directly into comedi's buffer.
+ * Also useful for retrieving a previously configured hardware device of
+ * known bus type. Set automatically for auto-configured devices.
+ * Automatically set to NULL when detaching hardware device.
+ */
+int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev);
+
+static inline unsigned int comedi_buf_n_bytes_ready(struct comedi_subdevice *s)
+{
+ return s->async->buf_write_count - s->async->buf_read_count;
+}
+
+unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, unsigned int n);
+unsigned int comedi_buf_write_free(struct comedi_subdevice *s, unsigned int n);
+
+unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s);
+unsigned int comedi_buf_read_alloc(struct comedi_subdevice *s, unsigned int n);
+unsigned int comedi_buf_read_free(struct comedi_subdevice *s, unsigned int n);
+
+unsigned int comedi_buf_write_samples(struct comedi_subdevice *s,
+ const void *data, unsigned int nsamples);
+unsigned int comedi_buf_read_samples(struct comedi_subdevice *s,
+ void *data, unsigned int nsamples);
+
+/* drivers.c - general comedi driver functions */
+
+#define COMEDI_TIMEOUT_MS 1000
+
+int comedi_timeout(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *,
+ int (*cb)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned long context),
+ unsigned long context);
+
+unsigned int comedi_handle_events(struct comedi_device *dev,
+ struct comedi_subdevice *s);
+
+int comedi_dio_insn_config(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data,
+ unsigned int mask);
+unsigned int comedi_dio_update_state(struct comedi_subdevice *,
+ unsigned int *data);
+unsigned int comedi_bytes_per_scan(struct comedi_subdevice *s);
+unsigned int comedi_nscans_left(struct comedi_subdevice *s,
+ unsigned int nscans);
+unsigned int comedi_nsamples_left(struct comedi_subdevice *s,
+ unsigned int nsamples);
+void comedi_inc_scan_progress(struct comedi_subdevice *s,
+ unsigned int num_bytes);
+
+void *comedi_alloc_devpriv(struct comedi_device *, size_t);
+int comedi_alloc_subdevices(struct comedi_device *, int);
+int comedi_alloc_subdev_readback(struct comedi_subdevice *);
+
+int comedi_readback_insn_read(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data);
+
+int comedi_load_firmware(struct comedi_device *, struct device *,
+ const char *name,
+ int (*cb)(struct comedi_device *,
+ const u8 *data, size_t size,
+ unsigned long context),
+ unsigned long context);
+
+int __comedi_request_region(struct comedi_device *,
+ unsigned long start, unsigned long len);
+int comedi_request_region(struct comedi_device *,
+ unsigned long start, unsigned long len);
+void comedi_legacy_detach(struct comedi_device *);
+
+int comedi_auto_config(struct device *, struct comedi_driver *,
+ unsigned long context);
+void comedi_auto_unconfig(struct device *);
+
+int comedi_driver_register(struct comedi_driver *);
+void comedi_driver_unregister(struct comedi_driver *);
+
+/**
+ * module_comedi_driver() - Helper macro for registering a comedi driver
+ * @__comedi_driver: comedi_driver struct
+ *
+ * Helper macro for comedi drivers which do not do anything special in module
+ * init/exit. This eliminates a lot of boilerplate. Each module may only use
+ * this macro once, and calling it replaces module_init() and module_exit().
+ */
+#define module_comedi_driver(__comedi_driver) \
+ module_driver(__comedi_driver, comedi_driver_register, \
+ comedi_driver_unregister)
+
+#endif /* _COMEDIDEV_H */
diff --git a/drivers/staging/comedi/comedilib.h b/drivers/staging/comedi/comedilib.h
new file mode 100644
index 000000000..56baf852e
--- /dev/null
+++ b/drivers/staging/comedi/comedilib.h
@@ -0,0 +1,35 @@
+/*
+ linux/include/comedilib.h
+ header file for kcomedilib
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998-2001 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _LINUX_COMEDILIB_H
+#define _LINUX_COMEDILIB_H
+
+struct comedi_device *comedi_open(const char *path);
+int comedi_close(struct comedi_device *dev);
+int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,
+ unsigned int chan, unsigned int *io);
+int comedi_dio_config(struct comedi_device *dev, unsigned int subdev,
+ unsigned int chan, unsigned int io);
+int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
+ unsigned int mask, unsigned int *bits,
+ unsigned int base_channel);
+int comedi_find_subdevice_by_type(struct comedi_device *dev, int type,
+ unsigned int subd);
+int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice);
+
+#endif
diff --git a/drivers/staging/comedi/drivers.c b/drivers/staging/comedi/drivers.c
new file mode 100644
index 000000000..f72a3ac9a
--- /dev/null
+++ b/drivers/staging/comedi/drivers.c
@@ -0,0 +1,956 @@
+/*
+ module/drivers.c
+ functions for manipulating drivers
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kconfig.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/highmem.h> /* for SuSE brokenness */
+#include <linux/vmalloc.h>
+#include <linux/cdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+
+#include "comedidev.h"
+#include "comedi_internal.h"
+
+struct comedi_driver *comedi_drivers;
+/* protects access to comedi_drivers */
+DEFINE_MUTEX(comedi_drivers_list_lock);
+
+int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev)
+{
+ if (hw_dev == dev->hw_dev)
+ return 0;
+ if (dev->hw_dev)
+ return -EEXIST;
+ dev->hw_dev = get_device(hw_dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_set_hw_dev);
+
+static void comedi_clear_hw_dev(struct comedi_device *dev)
+{
+ put_device(dev->hw_dev);
+ dev->hw_dev = NULL;
+}
+
+/**
+ * comedi_alloc_devpriv() - Allocate memory for the device private data.
+ * @dev: comedi_device struct
+ * @size: size of the memory to allocate
+ */
+void *comedi_alloc_devpriv(struct comedi_device *dev, size_t size)
+{
+ dev->private = kzalloc(size, GFP_KERNEL);
+ return dev->private;
+}
+EXPORT_SYMBOL_GPL(comedi_alloc_devpriv);
+
+int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices)
+{
+ struct comedi_subdevice *s;
+ int i;
+
+ if (num_subdevices < 1)
+ return -EINVAL;
+
+ s = kcalloc(num_subdevices, sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+ dev->subdevices = s;
+ dev->n_subdevices = num_subdevices;
+
+ for (i = 0; i < num_subdevices; ++i) {
+ s = &dev->subdevices[i];
+ s->device = dev;
+ s->index = i;
+ s->async_dma_dir = DMA_NONE;
+ spin_lock_init(&s->spin_lock);
+ s->minor = -1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_alloc_subdevices);
+
+/**
+ * comedi_alloc_subdev_readback() - Allocate memory for the subdevice readback.
+ * @s: comedi_subdevice struct
+ */
+int comedi_alloc_subdev_readback(struct comedi_subdevice *s)
+{
+ if (!s->n_chan)
+ return -EINVAL;
+
+ s->readback = kcalloc(s->n_chan, sizeof(*s->readback), GFP_KERNEL);
+ if (!s->readback)
+ return -ENOMEM;
+
+ if (!s->insn_read)
+ s->insn_read = comedi_readback_insn_read;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_alloc_subdev_readback);
+
+static void comedi_device_detach_cleanup(struct comedi_device *dev)
+{
+ int i;
+ struct comedi_subdevice *s;
+
+ if (dev->subdevices) {
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (s->runflags & COMEDI_SRF_FREE_SPRIV)
+ kfree(s->private);
+ comedi_free_subdevice_minor(s);
+ if (s->async) {
+ comedi_buf_alloc(dev, s, 0);
+ kfree(s->async);
+ }
+ kfree(s->readback);
+ }
+ kfree(dev->subdevices);
+ dev->subdevices = NULL;
+ dev->n_subdevices = 0;
+ }
+ kfree(dev->private);
+ kfree(dev->pacer);
+ dev->private = NULL;
+ dev->pacer = NULL;
+ dev->driver = NULL;
+ dev->board_name = NULL;
+ dev->board_ptr = NULL;
+ dev->mmio = NULL;
+ dev->iobase = 0;
+ dev->iolen = 0;
+ dev->ioenabled = false;
+ dev->irq = 0;
+ dev->read_subdev = NULL;
+ dev->write_subdev = NULL;
+ dev->open = NULL;
+ dev->close = NULL;
+ comedi_clear_hw_dev(dev);
+}
+
+void comedi_device_detach(struct comedi_device *dev)
+{
+ comedi_device_cancel_all(dev);
+ down_write(&dev->attach_lock);
+ dev->attached = false;
+ dev->detach_count++;
+ if (dev->driver)
+ dev->driver->detach(dev);
+ comedi_device_detach_cleanup(dev);
+ up_write(&dev->attach_lock);
+}
+
+static int poll_invalid(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ return -EINVAL;
+}
+
+int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ return -EINVAL;
+}
+
+/**
+ * comedi_readback_insn_read() - A generic (*insn_read) for subdevice readback.
+ * @dev: comedi_device struct
+ * @s: comedi_subdevice struct
+ * @insn: comedi_insn struct
+ * @data: pointer to return the readback data
+ */
+int comedi_readback_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ if (!s->readback)
+ return -EINVAL;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = s->readback[chan];
+
+ return insn->n;
+}
+EXPORT_SYMBOL_GPL(comedi_readback_insn_read);
+
+/**
+ * comedi_timeout() - busy-wait for a driver condition to occur.
+ * @dev: comedi_device struct
+ * @s: comedi_subdevice struct
+ * @insn: comedi_insn struct
+ * @cb: callback to check for the condition
+ * @context: private context from the driver
+ */
+int comedi_timeout(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ int (*cb)(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context),
+ unsigned long context)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(COMEDI_TIMEOUT_MS);
+ int ret;
+
+ while (time_before(jiffies, timeout)) {
+ ret = cb(dev, s, insn, context);
+ if (ret != -EBUSY)
+ return ret; /* success (0) or non EBUSY errno */
+ cpu_relax();
+ }
+ return -ETIMEDOUT;
+}
+EXPORT_SYMBOL_GPL(comedi_timeout);
+
+/**
+ * comedi_dio_insn_config() - boilerplate (*insn_config) for DIO subdevices.
+ * @dev: comedi_device struct
+ * @s: comedi_subdevice struct
+ * @insn: comedi_insn struct
+ * @data: parameters for the @insn
+ * @mask: io_bits mask for grouped channels
+ */
+int comedi_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data,
+ unsigned int mask)
+{
+ unsigned int chan_mask = 1 << CR_CHAN(insn->chanspec);
+
+ if (!mask)
+ mask = chan_mask;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIO_INPUT:
+ s->io_bits &= ~mask;
+ break;
+
+ case INSN_CONFIG_DIO_OUTPUT:
+ s->io_bits |= mask;
+ break;
+
+ case INSN_CONFIG_DIO_QUERY:
+ data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT;
+ return insn->n;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_insn_config);
+
+/**
+ * comedi_dio_update_state() - update the internal state of DIO subdevices.
+ * @s: comedi_subdevice struct
+ * @data: the channel mask and bits to update
+ */
+unsigned int comedi_dio_update_state(struct comedi_subdevice *s,
+ unsigned int *data)
+{
+ unsigned int chanmask = (s->n_chan < 32) ? ((1 << s->n_chan) - 1)
+ : 0xffffffff;
+ unsigned int mask = data[0] & chanmask;
+ unsigned int bits = data[1];
+
+ if (mask) {
+ s->state &= ~mask;
+ s->state |= (bits & mask);
+ }
+
+ return mask;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_update_state);
+
+/**
+ * comedi_bytes_per_scan - get length of asynchronous command "scan" in bytes
+ * @s: comedi_subdevice struct
+ *
+ * Determines the overall scan length according to the subdevice type and the
+ * number of channels in the scan.
+ *
+ * For digital input, output or input/output subdevices, samples for multiple
+ * channels are assumed to be packed into one or more unsigned short or
+ * unsigned int values according to the subdevice's SDF_LSAMPL flag. For other
+ * types of subdevice, samples are assumed to occupy a whole unsigned short or
+ * unsigned int according to the SDF_LSAMPL flag.
+ *
+ * Returns the overall scan length in bytes.
+ */
+unsigned int comedi_bytes_per_scan(struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int num_samples;
+ unsigned int bits_per_sample;
+
+ switch (s->type) {
+ case COMEDI_SUBD_DI:
+ case COMEDI_SUBD_DO:
+ case COMEDI_SUBD_DIO:
+ bits_per_sample = 8 * comedi_bytes_per_sample(s);
+ num_samples = DIV_ROUND_UP(cmd->scan_end_arg, bits_per_sample);
+ break;
+ default:
+ num_samples = cmd->scan_end_arg;
+ break;
+ }
+ return comedi_samples_to_bytes(s, num_samples);
+}
+EXPORT_SYMBOL_GPL(comedi_bytes_per_scan);
+
+/**
+ * comedi_nscans_left - return the number of scans left in the command
+ * @s: comedi_subdevice struct
+ * @nscans: the expected number of scans
+ *
+ * If nscans is 0, the number of scans available in the async buffer will be
+ * used. Otherwise the expected number of scans will be used.
+ *
+ * If the async command has a stop_src of TRIG_COUNT, the nscans will be
+ * checked against the number of scans left in the command.
+ *
+ * The return value will then be either the expected number of scans or the
+ * number of scans remaining in the command.
+ */
+unsigned int comedi_nscans_left(struct comedi_subdevice *s,
+ unsigned int nscans)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+
+ if (nscans == 0) {
+ unsigned int nbytes = comedi_buf_read_n_available(s);
+
+ nscans = nbytes / comedi_bytes_per_scan(s);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ unsigned int scans_left = 0;
+
+ if (async->scans_done < cmd->stop_arg)
+ scans_left = cmd->stop_arg - async->scans_done;
+
+ if (nscans > scans_left)
+ nscans = scans_left;
+ }
+ return nscans;
+}
+EXPORT_SYMBOL_GPL(comedi_nscans_left);
+
+/**
+ * comedi_nsamples_left - return the number of samples left in the command
+ * @s: comedi_subdevice struct
+ * @nsamples: the expected number of samples
+ *
+ * Returns the expected number of samples of the number of samples remaining
+ * in the command.
+ */
+unsigned int comedi_nsamples_left(struct comedi_subdevice *s,
+ unsigned int nsamples)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* +1 to force comedi_nscans_left() to return the scans left */
+ unsigned int nscans = (nsamples / cmd->scan_end_arg) + 1;
+ unsigned int scans_left = comedi_nscans_left(s, nscans);
+ unsigned int scan_pos =
+ comedi_bytes_to_samples(s, async->scan_progress);
+ unsigned long long samples_left = 0;
+
+ if (scans_left) {
+ samples_left = ((unsigned long long)scans_left *
+ cmd->scan_end_arg) - scan_pos;
+ }
+
+ if (samples_left < nsamples)
+ nsamples = samples_left;
+ }
+ return nsamples;
+}
+EXPORT_SYMBOL_GPL(comedi_nsamples_left);
+
+/**
+ * comedi_inc_scan_progress - update scan progress in asynchronous command
+ * @s: comedi_subdevice struct
+ * @num_bytes: amount of data in bytes to increment scan progress
+ *
+ * Increments the scan progress by the number of bytes specified by num_bytes.
+ * If the scan progress reaches or exceeds the scan length in bytes, reduce
+ * it modulo the scan length in bytes and set the "end of scan" asynchronous
+ * event flag to be processed later.
+ */
+void comedi_inc_scan_progress(struct comedi_subdevice *s,
+ unsigned int num_bytes)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int scan_length = comedi_bytes_per_scan(s);
+
+ /* track the 'cur_chan' for non-SDF_PACKED subdevices */
+ if (!(s->subdev_flags & SDF_PACKED)) {
+ async->cur_chan += comedi_bytes_to_samples(s, num_bytes);
+ async->cur_chan %= cmd->chanlist_len;
+ }
+
+ async->scan_progress += num_bytes;
+ if (async->scan_progress >= scan_length) {
+ unsigned int nscans = async->scan_progress / scan_length;
+
+ if (async->scans_done < (UINT_MAX - nscans))
+ async->scans_done += nscans;
+ else
+ async->scans_done = UINT_MAX;
+
+ async->scan_progress %= scan_length;
+ async->events |= COMEDI_CB_EOS;
+ }
+}
+EXPORT_SYMBOL_GPL(comedi_inc_scan_progress);
+
+/**
+ * comedi_handle_events - handle events and possibly stop acquisition
+ * @dev: comedi_device struct
+ * @s: comedi_subdevice struct
+ *
+ * Handles outstanding asynchronous acquisition event flags associated
+ * with the subdevice. Call the subdevice's "->cancel()" handler if the
+ * "end of acquisition", "error" or "overflow" event flags are set in order
+ * to stop the acquisition at the driver level.
+ *
+ * Calls comedi_event() to further process the event flags, which may mark
+ * the asynchronous command as no longer running, possibly terminated with
+ * an error, and may wake up tasks.
+ *
+ * Return a bit-mask of the handled events.
+ */
+unsigned int comedi_handle_events(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int events = s->async->events;
+
+ if (events == 0)
+ return events;
+
+ if (events & COMEDI_CB_CANCEL_MASK)
+ s->cancel(dev, s);
+
+ comedi_event(dev, s);
+
+ return events;
+}
+EXPORT_SYMBOL_GPL(comedi_handle_events);
+
+static int insn_rw_emulate_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct comedi_insn new_insn;
+ int ret;
+ static const unsigned channels_per_bitfield = 32;
+
+ unsigned chan = CR_CHAN(insn->chanspec);
+ const unsigned base_bitfield_channel =
+ (chan < channels_per_bitfield) ? 0 : chan;
+ unsigned int new_data[2];
+
+ memset(new_data, 0, sizeof(new_data));
+ memset(&new_insn, 0, sizeof(new_insn));
+ new_insn.insn = INSN_BITS;
+ new_insn.chanspec = base_bitfield_channel;
+ new_insn.n = 2;
+ new_insn.subdev = insn->subdev;
+
+ if (insn->insn == INSN_WRITE) {
+ if (!(s->subdev_flags & SDF_WRITABLE))
+ return -EINVAL;
+ new_data[0] = 1 << (chan - base_bitfield_channel); /* mask */
+ new_data[1] = data[0] ? (1 << (chan - base_bitfield_channel))
+ : 0; /* bits */
+ }
+
+ ret = s->insn_bits(dev, s, &new_insn, new_data);
+ if (ret < 0)
+ return ret;
+
+ if (insn->insn == INSN_READ)
+ data[0] = (new_data[1] >> (chan - base_bitfield_channel)) & 1;
+
+ return 1;
+}
+
+static int __comedi_device_postconfig_async(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async;
+ unsigned int buf_size;
+ int ret;
+
+ if ((s->subdev_flags & (SDF_CMD_READ | SDF_CMD_WRITE)) == 0) {
+ dev_warn(dev->class_dev,
+ "async subdevices must support SDF_CMD_READ or SDF_CMD_WRITE\n");
+ return -EINVAL;
+ }
+ if (!s->do_cmdtest) {
+ dev_warn(dev->class_dev,
+ "async subdevices must have a do_cmdtest() function\n");
+ return -EINVAL;
+ }
+
+ async = kzalloc(sizeof(*async), GFP_KERNEL);
+ if (!async)
+ return -ENOMEM;
+
+ init_waitqueue_head(&async->wait_head);
+ s->async = async;
+
+ async->max_bufsize = comedi_default_buf_maxsize_kb * 1024;
+ buf_size = comedi_default_buf_size_kb * 1024;
+ if (buf_size > async->max_bufsize)
+ buf_size = async->max_bufsize;
+
+ if (comedi_buf_alloc(dev, s, buf_size) < 0) {
+ dev_warn(dev->class_dev, "Buffer allocation failed\n");
+ return -ENOMEM;
+ }
+ if (s->buf_change) {
+ ret = s->buf_change(dev, s);
+ if (ret < 0)
+ return ret;
+ }
+
+ comedi_alloc_subdevice_minor(s);
+
+ return 0;
+}
+
+static int __comedi_device_postconfig(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+
+ if (s->type == COMEDI_SUBD_UNUSED)
+ continue;
+
+ if (s->type == COMEDI_SUBD_DO) {
+ if (s->n_chan < 32)
+ s->io_bits = (1 << s->n_chan) - 1;
+ else
+ s->io_bits = 0xffffffff;
+ }
+
+ if (s->len_chanlist == 0)
+ s->len_chanlist = 1;
+
+ if (s->do_cmd) {
+ ret = __comedi_device_postconfig_async(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ if (!s->range_table && !s->range_table_list)
+ s->range_table = &range_unknown;
+
+ if (!s->insn_read && s->insn_bits)
+ s->insn_read = insn_rw_emulate_bits;
+ if (!s->insn_write && s->insn_bits)
+ s->insn_write = insn_rw_emulate_bits;
+
+ if (!s->insn_read)
+ s->insn_read = insn_inval;
+ if (!s->insn_write)
+ s->insn_write = insn_inval;
+ if (!s->insn_bits)
+ s->insn_bits = insn_inval;
+ if (!s->insn_config)
+ s->insn_config = insn_inval;
+
+ if (!s->poll)
+ s->poll = poll_invalid;
+ }
+
+ return 0;
+}
+
+/* do a little post-config cleanup */
+static int comedi_device_postconfig(struct comedi_device *dev)
+{
+ int ret;
+
+ ret = __comedi_device_postconfig(dev);
+ if (ret < 0)
+ return ret;
+ down_write(&dev->attach_lock);
+ dev->attached = true;
+ up_write(&dev->attach_lock);
+ return 0;
+}
+
+/*
+ * Generic recognize function for drivers that register their supported
+ * board names.
+ *
+ * 'driv->board_name' points to a 'const char *' member within the
+ * zeroth element of an array of some private board information
+ * structure, say 'struct foo_board' containing a member 'const char
+ * *board_name' that is initialized to point to a board name string that
+ * is one of the candidates matched against this function's 'name'
+ * parameter.
+ *
+ * 'driv->offset' is the size of the private board information
+ * structure, say 'sizeof(struct foo_board)', and 'driv->num_names' is
+ * the length of the array of private board information structures.
+ *
+ * If one of the board names in the array of private board information
+ * structures matches the name supplied to this function, the function
+ * returns a pointer to the pointer to the board name, otherwise it
+ * returns NULL. The return value ends up in the 'board_ptr' member of
+ * a 'struct comedi_device' that the low-level comedi driver's
+ * 'attach()' hook can convert to a point to a particular element of its
+ * array of private board information structures by subtracting the
+ * offset of the member that points to the board name. (No subtraction
+ * is required if the board name pointer is the first member of the
+ * private board information structure, which is generally the case.)
+ */
+static void *comedi_recognize(struct comedi_driver *driv, const char *name)
+{
+ char **name_ptr = (char **)driv->board_name;
+ int i;
+
+ for (i = 0; i < driv->num_names; i++) {
+ if (strcmp(*name_ptr, name) == 0)
+ return name_ptr;
+ name_ptr = (void *)name_ptr + driv->offset;
+ }
+
+ return NULL;
+}
+
+static void comedi_report_boards(struct comedi_driver *driv)
+{
+ unsigned int i;
+ const char *const *name_ptr;
+
+ pr_info("comedi: valid board names for %s driver are:\n",
+ driv->driver_name);
+
+ name_ptr = driv->board_name;
+ for (i = 0; i < driv->num_names; i++) {
+ pr_info(" %s\n", *name_ptr);
+ name_ptr = (const char **)((char *)name_ptr + driv->offset);
+ }
+
+ if (driv->num_names == 0)
+ pr_info(" %s\n", driv->driver_name);
+}
+
+/**
+ * comedi_load_firmware() - Request and load firmware for a device.
+ * @dev: comedi_device struct
+ * @hw_device: device struct for the comedi_device
+ * @name: the name of the firmware image
+ * @cb: callback to the upload the firmware image
+ * @context: private context from the driver
+ */
+int comedi_load_firmware(struct comedi_device *dev,
+ struct device *device,
+ const char *name,
+ int (*cb)(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context),
+ unsigned long context)
+{
+ const struct firmware *fw;
+ int ret;
+
+ if (!cb)
+ return -EINVAL;
+
+ ret = maybe_reject_firmware(&fw, name, device);
+ if (ret == 0) {
+ ret = cb(dev, fw->data, fw->size, context);
+ release_firmware(fw);
+ }
+
+ return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(comedi_load_firmware);
+
+/**
+ * __comedi_request_region() - Request an I/O reqion for a legacy driver.
+ * @dev: comedi_device struct
+ * @start: base address of the I/O reqion
+ * @len: length of the I/O region
+ */
+int __comedi_request_region(struct comedi_device *dev,
+ unsigned long start, unsigned long len)
+{
+ if (!start) {
+ dev_warn(dev->class_dev,
+ "%s: a I/O base address must be specified\n",
+ dev->board_name);
+ return -EINVAL;
+ }
+
+ if (!request_region(start, len, dev->board_name)) {
+ dev_warn(dev->class_dev, "%s: I/O port conflict (%#lx,%lu)\n",
+ dev->board_name, start, len);
+ return -EIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__comedi_request_region);
+
+/**
+ * comedi_request_region() - Request an I/O reqion for a legacy driver.
+ * @dev: comedi_device struct
+ * @start: base address of the I/O reqion
+ * @len: length of the I/O region
+ */
+int comedi_request_region(struct comedi_device *dev,
+ unsigned long start, unsigned long len)
+{
+ int ret;
+
+ ret = __comedi_request_region(dev, start, len);
+ if (ret == 0) {
+ dev->iobase = start;
+ dev->iolen = len;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_request_region);
+
+/**
+ * comedi_legacy_detach() - A generic (*detach) function for legacy drivers.
+ * @dev: comedi_device struct
+ */
+void comedi_legacy_detach(struct comedi_device *dev)
+{
+ if (dev->irq) {
+ free_irq(dev->irq, dev);
+ dev->irq = 0;
+ }
+ if (dev->iobase && dev->iolen) {
+ release_region(dev->iobase, dev->iolen);
+ dev->iobase = 0;
+ dev->iolen = 0;
+ }
+}
+EXPORT_SYMBOL_GPL(comedi_legacy_detach);
+
+int comedi_device_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_driver *driv;
+ int ret;
+
+ if (dev->attached)
+ return -EBUSY;
+
+ mutex_lock(&comedi_drivers_list_lock);
+ for (driv = comedi_drivers; driv; driv = driv->next) {
+ if (!try_module_get(driv->module))
+ continue;
+ if (driv->num_names) {
+ dev->board_ptr = comedi_recognize(driv, it->board_name);
+ if (dev->board_ptr)
+ break;
+ } else if (strcmp(driv->driver_name, it->board_name) == 0) {
+ break;
+ }
+ module_put(driv->module);
+ }
+ if (!driv) {
+ /* recognize has failed if we get here */
+ /* report valid board names before returning error */
+ for (driv = comedi_drivers; driv; driv = driv->next) {
+ if (!try_module_get(driv->module))
+ continue;
+ comedi_report_boards(driv);
+ module_put(driv->module);
+ }
+ ret = -EIO;
+ goto out;
+ }
+ if (!driv->attach) {
+ /* driver does not support manual configuration */
+ dev_warn(dev->class_dev,
+ "driver '%s' does not support attach using comedi_config\n",
+ driv->driver_name);
+ module_put(driv->module);
+ ret = -ENOSYS;
+ goto out;
+ }
+ dev->driver = driv;
+ dev->board_name = dev->board_ptr ? *(const char **)dev->board_ptr
+ : dev->driver->driver_name;
+ ret = driv->attach(dev, it);
+ if (ret >= 0)
+ ret = comedi_device_postconfig(dev);
+ if (ret < 0) {
+ comedi_device_detach(dev);
+ module_put(driv->module);
+ }
+ /* On success, the driver module count has been incremented. */
+out:
+ mutex_unlock(&comedi_drivers_list_lock);
+ return ret;
+}
+
+int comedi_auto_config(struct device *hardware_device,
+ struct comedi_driver *driver, unsigned long context)
+{
+ struct comedi_device *dev;
+ int ret;
+
+ if (!hardware_device) {
+ pr_warn("BUG! comedi_auto_config called with NULL hardware_device\n");
+ return -EINVAL;
+ }
+ if (!driver) {
+ dev_warn(hardware_device,
+ "BUG! comedi_auto_config called with NULL comedi driver\n");
+ return -EINVAL;
+ }
+
+ if (!driver->auto_attach) {
+ dev_warn(hardware_device,
+ "BUG! comedi driver '%s' has no auto_attach handler\n",
+ driver->driver_name);
+ return -EINVAL;
+ }
+
+ dev = comedi_alloc_board_minor(hardware_device);
+ if (IS_ERR(dev)) {
+ dev_warn(hardware_device,
+ "driver '%s' could not create device.\n",
+ driver->driver_name);
+ return PTR_ERR(dev);
+ }
+ /* Note: comedi_alloc_board_minor() locked dev->mutex. */
+
+ dev->driver = driver;
+ dev->board_name = dev->driver->driver_name;
+ ret = driver->auto_attach(dev, context);
+ if (ret >= 0)
+ ret = comedi_device_postconfig(dev);
+ mutex_unlock(&dev->mutex);
+
+ if (ret < 0) {
+ dev_warn(hardware_device,
+ "driver '%s' failed to auto-configure device.\n",
+ driver->driver_name);
+ comedi_release_hardware_device(hardware_device);
+ } else {
+ /*
+ * class_dev should be set properly here
+ * after a successful auto config
+ */
+ dev_info(dev->class_dev,
+ "driver '%s' has successfully auto-configured '%s'.\n",
+ driver->driver_name, dev->board_name);
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_auto_config);
+
+void comedi_auto_unconfig(struct device *hardware_device)
+{
+ if (!hardware_device)
+ return;
+ comedi_release_hardware_device(hardware_device);
+}
+EXPORT_SYMBOL_GPL(comedi_auto_unconfig);
+
+int comedi_driver_register(struct comedi_driver *driver)
+{
+ mutex_lock(&comedi_drivers_list_lock);
+ driver->next = comedi_drivers;
+ comedi_drivers = driver;
+ mutex_unlock(&comedi_drivers_list_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_driver_register);
+
+void comedi_driver_unregister(struct comedi_driver *driver)
+{
+ struct comedi_driver *prev;
+ int i;
+
+ /* unlink the driver */
+ mutex_lock(&comedi_drivers_list_lock);
+ if (comedi_drivers == driver) {
+ comedi_drivers = driver->next;
+ } else {
+ for (prev = comedi_drivers; prev->next; prev = prev->next) {
+ if (prev->next == driver) {
+ prev->next = driver->next;
+ break;
+ }
+ }
+ }
+ mutex_unlock(&comedi_drivers_list_lock);
+
+ /* check for devices using this driver */
+ for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) {
+ struct comedi_device *dev = comedi_dev_get_from_minor(i);
+
+ if (!dev)
+ continue;
+
+ mutex_lock(&dev->mutex);
+ if (dev->attached && dev->driver == driver) {
+ if (dev->use_count)
+ dev_warn(dev->class_dev,
+ "BUG! detaching device with use_count=%d\n",
+ dev->use_count);
+ comedi_device_detach(dev);
+ }
+ mutex_unlock(&dev->mutex);
+ comedi_dev_put(dev);
+ }
+}
+EXPORT_SYMBOL_GPL(comedi_driver_unregister);
diff --git a/drivers/staging/comedi/drivers/8255.c b/drivers/staging/comedi/drivers/8255.c
new file mode 100644
index 000000000..ba89321df
--- /dev/null
+++ b/drivers/staging/comedi/drivers/8255.c
@@ -0,0 +1,342 @@
+/*
+ * comedi/drivers/8255.c
+ * Driver for 8255
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: 8255
+ * Description: generic 8255 support
+ * Devices: [standard] 8255 (8255)
+ * Author: ds
+ * Status: works
+ * Updated: Fri, 7 Jun 2002 12:56:45 -0700
+ *
+ * The classic in digital I/O. The 8255 appears in Comedi as a single
+ * digital I/O subdevice with 24 channels. The channel 0 corresponds
+ * to the 8255's port A, bit 0; channel 23 corresponds to port C, bit
+ * 7. Direction configuration is done in blocks, with channels 0-7,
+ * 8-15, 16-19, and 20-23 making up the 4 blocks. The only 8255 mode
+ * supported is mode 0.
+ *
+ * You should enable compilation this driver if you plan to use a board
+ * that has an 8255 chip. For multifunction boards, the main driver will
+ * configure the 8255 subdevice automatically.
+ *
+ * This driver also works independently with ISA and PCI cards that
+ * directly map the 8255 registers to I/O ports, including cards with
+ * multiple 8255 chips. To configure the driver for such a card, the
+ * option list should be a list of the I/O port bases for each of the
+ * 8255 chips. For example,
+ *
+ * comedi_config /dev/comedi0 8255 0x200,0x204,0x208,0x20c
+ *
+ * Note that most PCI 8255 boards do NOT work with this driver, and
+ * need a separate driver as a wrapper. For those that do work, the
+ * I/O port base address can be found in the output of 'lspci -v'.
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+
+struct subdev_8255_private {
+ unsigned long regbase;
+ int (*io)(struct comedi_device *, int, int, int, unsigned long);
+};
+
+static int subdev_8255_io(struct comedi_device *dev,
+ int dir, int port, int data, unsigned long regbase)
+{
+ if (dir) {
+ outb(data, dev->iobase + regbase + port);
+ return 0;
+ }
+ return inb(dev->iobase + regbase + port);
+}
+
+static int subdev_8255_mmio(struct comedi_device *dev,
+ int dir, int port, int data, unsigned long regbase)
+{
+ if (dir) {
+ writeb(data, dev->mmio + regbase + port);
+ return 0;
+ }
+ return readb(dev->mmio + regbase + port);
+}
+
+static int subdev_8255_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct subdev_8255_private *spriv = s->private;
+ unsigned long regbase = spriv->regbase;
+ unsigned int mask;
+ unsigned int v;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ spriv->io(dev, 1, I8255_DATA_A_REG,
+ s->state & 0xff, regbase);
+ if (mask & 0xff00)
+ spriv->io(dev, 1, I8255_DATA_B_REG,
+ (s->state >> 8) & 0xff, regbase);
+ if (mask & 0xff0000)
+ spriv->io(dev, 1, I8255_DATA_C_REG,
+ (s->state >> 16) & 0xff, regbase);
+ }
+
+ v = spriv->io(dev, 0, I8255_DATA_A_REG, 0, regbase);
+ v |= (spriv->io(dev, 0, I8255_DATA_B_REG, 0, regbase) << 8);
+ v |= (spriv->io(dev, 0, I8255_DATA_C_REG, 0, regbase) << 16);
+
+ data[1] = v;
+
+ return insn->n;
+}
+
+static void subdev_8255_do_config(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct subdev_8255_private *spriv = s->private;
+ unsigned long regbase = spriv->regbase;
+ int config;
+
+ config = I8255_CTRL_CW;
+ /* 1 in io_bits indicates output, 1 in config indicates input */
+ if (!(s->io_bits & 0x0000ff))
+ config |= I8255_CTRL_A_IO;
+ if (!(s->io_bits & 0x00ff00))
+ config |= I8255_CTRL_B_IO;
+ if (!(s->io_bits & 0x0f0000))
+ config |= I8255_CTRL_C_LO_IO;
+ if (!(s->io_bits & 0xf00000))
+ config |= I8255_CTRL_C_HI_IO;
+
+ spriv->io(dev, 1, I8255_CTRL_REG, config, regbase);
+}
+
+static int subdev_8255_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x0000ff;
+ else if (chan < 16)
+ mask = 0x00ff00;
+ else if (chan < 20)
+ mask = 0x0f0000;
+ else
+ mask = 0xf00000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ subdev_8255_do_config(dev, s);
+
+ return insn->n;
+}
+
+static int __subdev_8255_init(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ int (*io)(struct comedi_device *,
+ int, int, int, unsigned long),
+ unsigned long regbase,
+ bool is_mmio)
+{
+ struct subdev_8255_private *spriv;
+
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return -ENOMEM;
+
+ if (io)
+ spriv->io = io;
+ else if (is_mmio)
+ spriv->io = subdev_8255_mmio;
+ else
+ spriv->io = subdev_8255_io;
+ spriv->regbase = regbase;
+
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->range_table = &range_digital;
+ s->maxdata = 1;
+ s->insn_bits = subdev_8255_insn;
+ s->insn_config = subdev_8255_insn_config;
+
+ subdev_8255_do_config(dev, s);
+
+ return 0;
+}
+
+/**
+ * subdev_8255_init - initialize DIO subdevice for driving I/O mapped 8255
+ * @dev: comedi device owning subdevice
+ * @s: comedi subdevice to initialize
+ * @io: (optional) register I/O call-back function
+ * @regbase: offset of 8255 registers from dev->iobase, or call-back context
+ *
+ * Initializes a comedi subdevice as a DIO subdevice driving an 8255 chip.
+ *
+ * If the optional I/O call-back function is provided, its prototype is of
+ * the following form:
+ *
+ * int my_8255_callback(struct comedi_device *dev,
+ * struct comedi_subdevice *s, int dir, int port,
+ * int data, unsigned long regbase);
+ *
+ * where 'dev', 's', and 'regbase' match the values passed to this function,
+ * 'port' is the 8255 port number 0 to 3 (including the control port), 'dir'
+ * is the direction (0 for read, 1 for write) and 'data' is the value to be
+ * written. It should return 0 if writing or the value read if reading.
+ *
+ * If the optional I/O call-back function is not provided, an internal
+ * call-back function is used which uses consecutive I/O port addresses
+ * starting at dev->iobase + regbase.
+ *
+ * Return: -ENOMEM if failed to allocate memory, zero on success.
+ */
+int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s,
+ int (*io)(struct comedi_device *,
+ int, int, int, unsigned long),
+ unsigned long regbase)
+{
+ return __subdev_8255_init(dev, s, io, regbase, false);
+}
+EXPORT_SYMBOL_GPL(subdev_8255_init);
+
+/**
+ * subdev_8255_mm_init - initialize DIO subdevice for driving mmio-mapped 8255
+ * @dev: comedi device owning subdevice
+ * @s: comedi subdevice to initialize
+ * @io: (optional) register I/O call-back function
+ * @regbase: offset of 8255 registers from dev->mmio, or call-back context
+ *
+ * Initializes a comedi subdevice as a DIO subdevice driving an 8255 chip.
+ *
+ * If the optional I/O call-back function is provided, its prototype is of
+ * the following form:
+ *
+ * int my_8255_callback(struct comedi_device *dev,
+ * struct comedi_subdevice *s, int dir, int port,
+ * int data, unsigned long regbase);
+ *
+ * where 'dev', 's', and 'regbase' match the values passed to this function,
+ * 'port' is the 8255 port number 0 to 3 (including the control port), 'dir'
+ * is the direction (0 for read, 1 for write) and 'data' is the value to be
+ * written. It should return 0 if writing or the value read if reading.
+ *
+ * If the optional I/O call-back function is not provided, an internal
+ * call-back function is used which uses consecutive MMIO virtual addresses
+ * starting at dev->mmio + regbase.
+ *
+ * Return: -ENOMEM if failed to allocate memory, zero on success.
+ */
+int subdev_8255_mm_init(struct comedi_device *dev, struct comedi_subdevice *s,
+ int (*io)(struct comedi_device *,
+ int, int, int, unsigned long),
+ unsigned long regbase)
+{
+ return __subdev_8255_init(dev, s, io, regbase, true);
+}
+EXPORT_SYMBOL_GPL(subdev_8255_mm_init);
+
+/*
+ * Start of the 8255 standalone device
+ */
+
+static int dev_8255_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ unsigned long iobase;
+ int ret;
+ int i;
+
+ for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) {
+ iobase = it->options[i];
+ if (!iobase)
+ break;
+ }
+ if (i == 0) {
+ dev_warn(dev->class_dev, "no devices specified\n");
+ return -EINVAL;
+ }
+
+ ret = comedi_alloc_subdevices(dev, i);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ iobase = it->options[i];
+
+ /*
+ * __comedi_request_region() does not set dev->iobase.
+ *
+ * For 8255 devices that are manually attached using
+ * comedi_config, the 'iobase' is the actual I/O port
+ * base address of the chip.
+ */
+ ret = __comedi_request_region(dev, iobase, I8255_SIZE);
+ if (ret) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = subdev_8255_init(dev, s, NULL, iobase);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void dev_8255_detach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ struct subdev_8255_private *spriv;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (s->type != COMEDI_SUBD_UNUSED) {
+ spriv = s->private;
+ release_region(spriv->regbase, I8255_SIZE);
+ }
+ }
+}
+
+static struct comedi_driver dev_8255_driver = {
+ .driver_name = "8255",
+ .module = THIS_MODULE,
+ .attach = dev_8255_attach,
+ .detach = dev_8255_detach,
+};
+module_comedi_driver(dev_8255_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/8255.h b/drivers/staging/comedi/drivers/8255.h
new file mode 100644
index 000000000..934b940eb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/8255.h
@@ -0,0 +1,48 @@
+/*
+ * module/8255.h
+ * Header file for 8255
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _8255_H
+#define _8255_H
+
+#include "../comedidev.h"
+
+#define I8255_SIZE 0x04
+
+#define I8255_DATA_A_REG 0x00
+#define I8255_DATA_B_REG 0x01
+#define I8255_DATA_C_REG 0x02
+#define I8255_CTRL_REG 0x03
+#define I8255_CTRL_C_LO_IO (1 << 0)
+#define I8255_CTRL_B_IO (1 << 1)
+#define I8255_CTRL_B_MODE (1 << 2)
+#define I8255_CTRL_C_HI_IO (1 << 3)
+#define I8255_CTRL_A_IO (1 << 4)
+#define I8255_CTRL_A_MODE(x) ((x) << 5)
+#define I8255_CTRL_CW (1 << 7)
+
+int subdev_8255_init(struct comedi_device *, struct comedi_subdevice *,
+ int (*io)(struct comedi_device *,
+ int, int, int, unsigned long),
+ unsigned long regbase);
+
+int subdev_8255_mm_init(struct comedi_device *, struct comedi_subdevice *,
+ int (*io)(struct comedi_device *,
+ int, int, int, unsigned long),
+ unsigned long regbase);
+
+#endif
diff --git a/drivers/staging/comedi/drivers/8255_pci.c b/drivers/staging/comedi/drivers/8255_pci.c
new file mode 100644
index 000000000..bb9854b56
--- /dev/null
+++ b/drivers/staging/comedi/drivers/8255_pci.c
@@ -0,0 +1,304 @@
+/*
+ * COMEDI driver for generic PCI based 8255 digital i/o boards
+ * Copyright (C) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on the tested adl_pci7296 driver written by:
+ * Jon Grierson <jd@renko.co.uk>
+ * and the experimental cb_pcidio driver written by:
+ * Yoshiya Matsuzaka
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: 8255_pci
+ * Description: Generic PCI based 8255 Digital I/O boards
+ * Devices: [ADLink] PCI-7224 (adl_pci-7224), PCI-7248 (adl_pci-7248),
+ * PCI-7296 (adl_pci-7296),
+ * [Measurement Computing] PCI-DIO24 (cb_pci-dio24),
+ * PCI-DIO24H (cb_pci-dio24h), PCI-DIO48H (cb_pci-dio48h),
+ * PCI-DIO96H (cb_pci-dio96h),
+ * [National Instruments] PCI-DIO-96 (ni_pci-dio-96),
+ * PCI-DIO-96B (ni_pci-dio-96b), PXI-6508 (ni_pxi-6508),
+ * PCI-6503 (ni_pci-6503), PCI-6503B (ni_pci-6503b),
+ * PCI-6503X (ni_pci-6503x), PXI-6503 (ni_pxi-6503)
+ * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Updated: Wed, 12 Sep 2012 11:52:01 -0700
+ * Status: untested
+ *
+ * These boards have one or more 8255 digital I/O chips, each of which
+ * is supported as a separate 24-channel DIO subdevice.
+ *
+ * Boards with 24 DIO channels (1 DIO subdevice):
+ *
+ * PCI-7224, PCI-DIO24, PCI-DIO24H, PCI-6503, PCI-6503B, PCI-6503X,
+ * PXI-6503
+ *
+ * Boards with 48 DIO channels (2 DIO subdevices):
+ *
+ * PCI-7248, PCI-DIO48H
+ *
+ * Boards with 96 DIO channels (4 DIO subdevices):
+ *
+ * PCI-7296, PCI-DIO96H, PCI-DIO-96, PCI-DIO-96B, PXI-6508
+ *
+ * Some of these boards also have an 8254 programmable timer/counter
+ * chip. This chip is not currently supported by this driver.
+ *
+ * Interrupt support for these boards is also not currently supported.
+ *
+ * Configuration Options: not applicable, uses PCI auto config.
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+#include "8255.h"
+
+enum pci_8255_boardid {
+ BOARD_ADLINK_PCI7224,
+ BOARD_ADLINK_PCI7248,
+ BOARD_ADLINK_PCI7296,
+ BOARD_CB_PCIDIO24,
+ BOARD_CB_PCIDIO24H,
+ BOARD_CB_PCIDIO48H_OLD,
+ BOARD_CB_PCIDIO48H_NEW,
+ BOARD_CB_PCIDIO96H,
+ BOARD_NI_PCIDIO96,
+ BOARD_NI_PCIDIO96B,
+ BOARD_NI_PXI6508,
+ BOARD_NI_PCI6503,
+ BOARD_NI_PCI6503B,
+ BOARD_NI_PCI6503X,
+ BOARD_NI_PXI_6503,
+};
+
+struct pci_8255_boardinfo {
+ const char *name;
+ int dio_badr;
+ int n_8255;
+ unsigned int has_mite:1;
+};
+
+static const struct pci_8255_boardinfo pci_8255_boards[] = {
+ [BOARD_ADLINK_PCI7224] = {
+ .name = "adl_pci-7224",
+ .dio_badr = 2,
+ .n_8255 = 1,
+ },
+ [BOARD_ADLINK_PCI7248] = {
+ .name = "adl_pci-7248",
+ .dio_badr = 2,
+ .n_8255 = 2,
+ },
+ [BOARD_ADLINK_PCI7296] = {
+ .name = "adl_pci-7296",
+ .dio_badr = 2,
+ .n_8255 = 4,
+ },
+ [BOARD_CB_PCIDIO24] = {
+ .name = "cb_pci-dio24",
+ .dio_badr = 2,
+ .n_8255 = 1,
+ },
+ [BOARD_CB_PCIDIO24H] = {
+ .name = "cb_pci-dio24h",
+ .dio_badr = 2,
+ .n_8255 = 1,
+ },
+ [BOARD_CB_PCIDIO48H_OLD] = {
+ .name = "cb_pci-dio48h",
+ .dio_badr = 1,
+ .n_8255 = 2,
+ },
+ [BOARD_CB_PCIDIO48H_NEW] = {
+ .name = "cb_pci-dio48h",
+ .dio_badr = 2,
+ .n_8255 = 2,
+ },
+ [BOARD_CB_PCIDIO96H] = {
+ .name = "cb_pci-dio96h",
+ .dio_badr = 2,
+ .n_8255 = 4,
+ },
+ [BOARD_NI_PCIDIO96] = {
+ .name = "ni_pci-dio-96",
+ .dio_badr = 1,
+ .n_8255 = 4,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCIDIO96B] = {
+ .name = "ni_pci-dio-96b",
+ .dio_badr = 1,
+ .n_8255 = 4,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PXI6508] = {
+ .name = "ni_pxi-6508",
+ .dio_badr = 1,
+ .n_8255 = 4,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCI6503] = {
+ .name = "ni_pci-6503",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCI6503B] = {
+ .name = "ni_pci-6503b",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCI6503X] = {
+ .name = "ni_pci-6503x",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PXI_6503] = {
+ .name = "ni_pxi-6503",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+};
+
+/* ripped from mite.h and mite_setup2() to avoid mite dependency */
+#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */
+#define WENAB (1 << 7) /* window enable */
+
+static int pci_8255_mite_init(struct pci_dev *pcidev)
+{
+ void __iomem *mite_base;
+ u32 main_phys_addr;
+
+ /* ioremap the MITE registers (BAR 0) temporarily */
+ mite_base = pci_ioremap_bar(pcidev, 0);
+ if (!mite_base)
+ return -ENOMEM;
+
+ /* set data window to main registers (BAR 1) */
+ main_phys_addr = pci_resource_start(pcidev, 1);
+ writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR);
+
+ /* finished with MITE registers */
+ iounmap(mite_base);
+ return 0;
+}
+
+static int pci_8255_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct pci_8255_boardinfo *board = NULL;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ if (context < ARRAY_SIZE(pci_8255_boards))
+ board = &pci_8255_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ if (board->has_mite) {
+ ret = pci_8255_mite_init(pcidev);
+ if (ret)
+ return ret;
+ }
+
+ if ((pci_resource_flags(pcidev, board->dio_badr) & IORESOURCE_MEM)) {
+ dev->mmio = pci_ioremap_bar(pcidev, board->dio_badr);
+ if (!dev->mmio)
+ return -ENOMEM;
+ } else {
+ dev->iobase = pci_resource_start(pcidev, board->dio_badr);
+ }
+
+ /*
+ * One, two, or four subdevices are setup by this driver depending
+ * on the number of channels provided by the board. Each subdevice
+ * has 24 channels supported by the 8255 module.
+ */
+ ret = comedi_alloc_subdevices(dev, board->n_8255);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < board->n_8255; i++) {
+ s = &dev->subdevices[i];
+ if (dev->mmio)
+ ret = subdev_8255_mm_init(dev, s, NULL, i * I8255_SIZE);
+ else
+ ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver pci_8255_driver = {
+ .driver_name = "8255_pci",
+ .module = THIS_MODULE,
+ .auto_attach = pci_8255_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int pci_8255_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &pci_8255_driver, id->driver_data);
+}
+
+static const struct pci_device_id pci_8255_pci_table[] = {
+ { PCI_VDEVICE(ADLINK, 0x7224), BOARD_ADLINK_PCI7224 },
+ { PCI_VDEVICE(ADLINK, 0x7248), BOARD_ADLINK_PCI7248 },
+ { PCI_VDEVICE(ADLINK, 0x7296), BOARD_ADLINK_PCI7296 },
+ { PCI_VDEVICE(CB, 0x0028), BOARD_CB_PCIDIO24 },
+ { PCI_VDEVICE(CB, 0x0014), BOARD_CB_PCIDIO24H },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_CB, 0x000b, 0x0000, 0x0000),
+ .driver_data = BOARD_CB_PCIDIO48H_OLD },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_CB, 0x000b, PCI_VENDOR_ID_CB, 0x000b),
+ .driver_data = BOARD_CB_PCIDIO48H_NEW },
+ { PCI_VDEVICE(CB, 0x0017), BOARD_CB_PCIDIO96H },
+ { PCI_VDEVICE(NI, 0x0160), BOARD_NI_PCIDIO96 },
+ { PCI_VDEVICE(NI, 0x1630), BOARD_NI_PCIDIO96B },
+ { PCI_VDEVICE(NI, 0x13c0), BOARD_NI_PXI6508 },
+ { PCI_VDEVICE(NI, 0x0400), BOARD_NI_PCI6503 },
+ { PCI_VDEVICE(NI, 0x1250), BOARD_NI_PCI6503B },
+ { PCI_VDEVICE(NI, 0x17d0), BOARD_NI_PCI6503X },
+ { PCI_VDEVICE(NI, 0x1800), BOARD_NI_PXI_6503 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, pci_8255_pci_table);
+
+static struct pci_driver pci_8255_pci_driver = {
+ .name = "8255_pci",
+ .id_table = pci_8255_pci_table,
+ .probe = pci_8255_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(pci_8255_driver, pci_8255_pci_driver);
+
+MODULE_DESCRIPTION("COMEDI - Generic PCI based 8255 Digital I/O boards");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile
new file mode 100644
index 000000000..d6d834006
--- /dev/null
+++ b/drivers/staging/comedi/drivers/Makefile
@@ -0,0 +1,145 @@
+# Makefile for individual comedi drivers
+#
+ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG
+
+# Comedi "helper" modules
+obj-$(CONFIG_COMEDI_8254) += comedi_8254.o
+obj-$(CONFIG_COMEDI_ISADMA) += comedi_isadma.o
+
+# Comedi misc drivers
+obj-$(CONFIG_COMEDI_BOND) += comedi_bond.o
+obj-$(CONFIG_COMEDI_TEST) += comedi_test.o
+obj-$(CONFIG_COMEDI_PARPORT) += comedi_parport.o
+obj-$(CONFIG_COMEDI_SERIAL2002) += serial2002.o
+
+# Comedi ISA drivers
+obj-$(CONFIG_COMEDI_AMPLC_DIO200_ISA) += amplc_dio200.o
+obj-$(CONFIG_COMEDI_AMPLC_PC236_ISA) += amplc_pc236.o
+obj-$(CONFIG_COMEDI_AMPLC_PC263_ISA) += amplc_pc263.o
+obj-$(CONFIG_COMEDI_PCL711) += pcl711.o
+obj-$(CONFIG_COMEDI_PCL724) += pcl724.o
+obj-$(CONFIG_COMEDI_PCL726) += pcl726.o
+obj-$(CONFIG_COMEDI_PCL730) += pcl730.o
+obj-$(CONFIG_COMEDI_PCL812) += pcl812.o
+obj-$(CONFIG_COMEDI_PCL816) += pcl816.o
+obj-$(CONFIG_COMEDI_PCL818) += pcl818.o
+obj-$(CONFIG_COMEDI_PCM3724) += pcm3724.o
+obj-$(CONFIG_COMEDI_RTI800) += rti800.o
+obj-$(CONFIG_COMEDI_RTI802) += rti802.o
+obj-$(CONFIG_COMEDI_DAC02) += dac02.o
+obj-$(CONFIG_COMEDI_DAS16M1) += das16m1.o
+obj-$(CONFIG_COMEDI_DAS08_ISA) += das08_isa.o
+obj-$(CONFIG_COMEDI_DAS16) += das16.o
+obj-$(CONFIG_COMEDI_DAS800) += das800.o
+obj-$(CONFIG_COMEDI_DAS1800) += das1800.o
+obj-$(CONFIG_COMEDI_DAS6402) += das6402.o
+obj-$(CONFIG_COMEDI_DT2801) += dt2801.o
+obj-$(CONFIG_COMEDI_DT2811) += dt2811.o
+obj-$(CONFIG_COMEDI_DT2814) += dt2814.o
+obj-$(CONFIG_COMEDI_DT2815) += dt2815.o
+obj-$(CONFIG_COMEDI_DT2817) += dt2817.o
+obj-$(CONFIG_COMEDI_DT282X) += dt282x.o
+obj-$(CONFIG_COMEDI_DMM32AT) += dmm32at.o
+obj-$(CONFIG_COMEDI_FL512) += fl512.o
+obj-$(CONFIG_COMEDI_AIO_AIO12_8) += aio_aio12_8.o
+obj-$(CONFIG_COMEDI_AIO_IIRO_16) += aio_iiro_16.o
+obj-$(CONFIG_COMEDI_II_PCI20KC) += ii_pci20kc.o
+obj-$(CONFIG_COMEDI_C6XDIGIO) += c6xdigio.o
+obj-$(CONFIG_COMEDI_MPC624) += mpc624.o
+obj-$(CONFIG_COMEDI_ADQ12B) += adq12b.o
+obj-$(CONFIG_COMEDI_NI_AT_A2150) += ni_at_a2150.o
+obj-$(CONFIG_COMEDI_NI_AT_AO) += ni_at_ao.o
+obj-$(CONFIG_COMEDI_NI_ATMIO) += ni_atmio.o
+obj-$(CONFIG_COMEDI_NI_ATMIO16D) += ni_atmio16d.o
+obj-$(CONFIG_COMEDI_NI_LABPC_ISA) += ni_labpc.o
+obj-$(CONFIG_COMEDI_PCMAD) += pcmad.o
+obj-$(CONFIG_COMEDI_PCMDA12) += pcmda12.o
+obj-$(CONFIG_COMEDI_PCMMIO) += pcmmio.o
+obj-$(CONFIG_COMEDI_PCMUIO) += pcmuio.o
+obj-$(CONFIG_COMEDI_MULTIQ3) += multiq3.o
+obj-$(CONFIG_COMEDI_S526) += s526.o
+
+# Comedi PCI drivers
+obj-$(CONFIG_COMEDI_8255_PCI) += 8255_pci.o
+obj-$(CONFIG_COMEDI_ADDI_WATCHDOG) += addi_watchdog.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_1032) += addi_apci_1032.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_1500) += addi_apci_1500.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_1516) += addi_apci_1516.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_1564) += addi_apci_1564.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_16XX) += addi_apci_16xx.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_2032) += addi_apci_2032.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_2200) += addi_apci_2200.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_3120) += addi_apci_3120.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_3501) += addi_apci_3501.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_3XXX) += addi_apci_3xxx.o
+obj-$(CONFIG_COMEDI_ADL_PCI6208) += adl_pci6208.o
+obj-$(CONFIG_COMEDI_ADL_PCI7X3X) += adl_pci7x3x.o
+obj-$(CONFIG_COMEDI_ADL_PCI8164) += adl_pci8164.o
+obj-$(CONFIG_COMEDI_ADL_PCI9111) += adl_pci9111.o
+obj-$(CONFIG_COMEDI_ADL_PCI9118) += adl_pci9118.o
+obj-$(CONFIG_COMEDI_ADV_PCI1710) += adv_pci1710.o
+obj-$(CONFIG_COMEDI_ADV_PCI1723) += adv_pci1723.o
+obj-$(CONFIG_COMEDI_ADV_PCI1724) += adv_pci1724.o
+obj-$(CONFIG_COMEDI_ADV_PCI_DIO) += adv_pci_dio.o
+obj-$(CONFIG_COMEDI_AMPLC_DIO200_PCI) += amplc_dio200_pci.o
+obj-$(CONFIG_COMEDI_AMPLC_PC236_PCI) += amplc_pci236.o
+obj-$(CONFIG_COMEDI_AMPLC_PC263_PCI) += amplc_pci263.o
+obj-$(CONFIG_COMEDI_AMPLC_PCI224) += amplc_pci224.o
+obj-$(CONFIG_COMEDI_AMPLC_PCI230) += amplc_pci230.o
+obj-$(CONFIG_COMEDI_CONTEC_PCI_DIO) += contec_pci_dio.o
+obj-$(CONFIG_COMEDI_DAS08_PCI) += das08_pci.o
+obj-$(CONFIG_COMEDI_DT3000) += dt3000.o
+obj-$(CONFIG_COMEDI_DYNA_PCI10XX) += dyna_pci10xx.o
+obj-$(CONFIG_COMEDI_UNIOXX5) += unioxx5.o
+obj-$(CONFIG_COMEDI_GSC_HPDI) += gsc_hpdi.o
+obj-$(CONFIG_COMEDI_ICP_MULTI) += icp_multi.o
+obj-$(CONFIG_COMEDI_DAQBOARD2000) += daqboard2000.o
+obj-$(CONFIG_COMEDI_JR3_PCI) += jr3_pci.o
+obj-$(CONFIG_COMEDI_KE_COUNTER) += ke_counter.o
+obj-$(CONFIG_COMEDI_CB_PCIDAS64) += cb_pcidas64.o
+obj-$(CONFIG_COMEDI_CB_PCIDAS) += cb_pcidas.o
+obj-$(CONFIG_COMEDI_CB_PCIDDA) += cb_pcidda.o
+obj-$(CONFIG_COMEDI_CB_PCIMDAS) += cb_pcimdas.o
+obj-$(CONFIG_COMEDI_CB_PCIMDDA) += cb_pcimdda.o
+obj-$(CONFIG_COMEDI_ME4000) += me4000.o
+obj-$(CONFIG_COMEDI_ME_DAQ) += me_daq.o
+obj-$(CONFIG_COMEDI_NI_6527) += ni_6527.o
+obj-$(CONFIG_COMEDI_NI_65XX) += ni_65xx.o
+obj-$(CONFIG_COMEDI_NI_660X) += ni_660x.o
+obj-$(CONFIG_COMEDI_NI_670X) += ni_670x.o
+obj-$(CONFIG_COMEDI_NI_LABPC_PCI) += ni_labpc_pci.o
+obj-$(CONFIG_COMEDI_NI_PCIDIO) += ni_pcidio.o
+obj-$(CONFIG_COMEDI_NI_PCIMIO) += ni_pcimio.o
+obj-$(CONFIG_COMEDI_RTD520) += rtd520.o
+obj-$(CONFIG_COMEDI_S626) += s626.o
+obj-$(CONFIG_COMEDI_SSV_DNP) += ssv_dnp.o
+obj-$(CONFIG_COMEDI_MF6X4) += mf6x4.o
+
+# Comedi PCMCIA drivers
+obj-$(CONFIG_COMEDI_CB_DAS16_CS) += cb_das16_cs.o
+obj-$(CONFIG_COMEDI_DAS08_CS) += das08_cs.o
+obj-$(CONFIG_COMEDI_NI_DAQ_700_CS) += ni_daq_700.o
+obj-$(CONFIG_COMEDI_NI_DAQ_DIO24_CS) += ni_daq_dio24.o
+obj-$(CONFIG_COMEDI_NI_LABPC_CS) += ni_labpc_cs.o
+obj-$(CONFIG_COMEDI_NI_MIO_CS) += ni_mio_cs.o
+obj-$(CONFIG_COMEDI_QUATECH_DAQP_CS) += quatech_daqp_cs.o
+
+# Comedi USB drivers
+obj-$(CONFIG_COMEDI_DT9812) += dt9812.o
+obj-$(CONFIG_COMEDI_NI_USB6501) += ni_usb6501.o
+obj-$(CONFIG_COMEDI_USBDUX) += usbdux.o
+obj-$(CONFIG_COMEDI_USBDUXFAST) += usbduxfast.o
+obj-$(CONFIG_COMEDI_USBDUXSIGMA) += usbduxsigma.o
+obj-$(CONFIG_COMEDI_VMK80XX) += vmk80xx.o
+
+# Comedi NI drivers
+obj-$(CONFIG_COMEDI_MITE) += mite.o
+obj-$(CONFIG_COMEDI_NI_TIO) += ni_tio.o
+obj-$(CONFIG_COMEDI_NI_TIOCMD) += ni_tiocmd.o
+obj-$(CONFIG_COMEDI_NI_LABPC) += ni_labpc_common.o
+obj-$(CONFIG_COMEDI_NI_LABPC_ISADMA) += ni_labpc_isadma.o
+
+obj-$(CONFIG_COMEDI_8255) += 8255.o
+obj-$(CONFIG_COMEDI_AMPLC_DIO200) += amplc_dio200_common.o
+obj-$(CONFIG_COMEDI_AMPLC_PC236) += amplc_pc236_common.o
+obj-$(CONFIG_COMEDI_DAS08) += das08.o
diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c
new file mode 100644
index 000000000..fa99c8ca4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c
@@ -0,0 +1,204 @@
+/* Digital Input IRQ Function Selection */
+#define APCI1564_DI_INT_OR (0 << 1)
+#define APCI1564_DI_INT_AND (1 << 1)
+
+/* Digital Input Interrupt Enable Disable. */
+#define APCI1564_DI_INT_ENABLE 0x4
+#define APCI1564_DI_INT_DISABLE 0xfffffffb
+
+/* Digital Output Interrupt Enable Disable. */
+#define APCI1564_DO_VCC_INT_ENABLE 0x1
+#define APCI1564_DO_VCC_INT_DISABLE 0xfffffffe
+#define APCI1564_DO_CC_INT_ENABLE 0x2
+#define APCI1564_DO_CC_INT_DISABLE 0xfffffffd
+
+/* TIMER COUNTER WATCHDOG DEFINES */
+#define ADDIDATA_TIMER 0
+#define ADDIDATA_COUNTER 1
+#define ADDIDATA_WATCHDOG 2
+
+static int apci1564_timer_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1564_private *devpriv = dev->private;
+ unsigned int ctrl;
+
+ devpriv->tsk_current = current;
+
+ /* First Stop The Timer */
+ ctrl = inl(devpriv->timer + ADDI_TCW_CTRL_REG);
+ ctrl &= 0xfffff9fe;
+ /* Stop The Timer */
+ outl(ctrl, devpriv->timer + ADDI_TCW_CTRL_REG);
+
+ if (data[1] == 1) {
+ /* Enable timer int & disable all the other int sources */
+ outl(0x02, devpriv->timer + ADDI_TCW_CTRL_REG);
+ outl(0x0, dev->iobase + APCI1564_DI_IRQ_REG);
+ outl(0x0, dev->iobase + APCI1564_DO_IRQ_REG);
+ outl(0x0, dev->iobase + APCI1564_WDOG_IRQ_REG);
+ if (devpriv->counters) {
+ unsigned long iobase;
+
+ iobase = devpriv->counters + ADDI_TCW_IRQ_REG;
+ outl(0x0, iobase + APCI1564_COUNTER(0));
+ outl(0x0, iobase + APCI1564_COUNTER(1));
+ outl(0x0, iobase + APCI1564_COUNTER(2));
+ }
+ } else {
+ /* disable Timer interrupt */
+ outl(0x0, devpriv->timer + ADDI_TCW_CTRL_REG);
+ }
+
+ /* Loading Timebase */
+ outl(data[2], devpriv->timer + ADDI_TCW_TIMEBASE_REG);
+
+ /* Loading the Reload value */
+ outl(data[3], devpriv->timer + ADDI_TCW_RELOAD_REG);
+
+ ctrl = inl(devpriv->timer + ADDI_TCW_CTRL_REG);
+ ctrl &= 0xfff719e2;
+ ctrl |= (2 << 13) | 0x10;
+ /* mode 2 */
+ outl(ctrl, devpriv->timer + ADDI_TCW_CTRL_REG);
+
+ return insn->n;
+}
+
+static int apci1564_timer_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1564_private *devpriv = dev->private;
+ unsigned int ctrl;
+
+ ctrl = inl(devpriv->timer + ADDI_TCW_CTRL_REG);
+ switch (data[1]) {
+ case 0: /* Stop The Timer */
+ ctrl &= 0xfffff9fe;
+ break;
+ case 1: /* Enable the Timer */
+ ctrl &= 0xfffff9ff;
+ ctrl |= 0x1;
+ break;
+ }
+ outl(ctrl, devpriv->timer + ADDI_TCW_CTRL_REG);
+
+ return insn->n;
+}
+
+static int apci1564_timer_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1564_private *devpriv = dev->private;
+
+ /* Stores the status of the Timer */
+ data[0] = inl(devpriv->timer + ADDI_TCW_STATUS_REG) & 0x1;
+
+ /* Stores the Actual value of the Timer */
+ data[1] = inl(devpriv->timer + ADDI_TCW_VAL_REG);
+
+ return insn->n;
+}
+
+static int apci1564_counter_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1564_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan);
+ unsigned int ctrl;
+
+ devpriv->tsk_current = current;
+
+ /* First Stop The Counter */
+ ctrl = inl(iobase + ADDI_TCW_CTRL_REG);
+ ctrl &= 0xfffff9fe;
+ /* Stop The Timer */
+ outl(ctrl, iobase + ADDI_TCW_CTRL_REG);
+
+ /* Set the reload value */
+ outl(data[3], iobase + ADDI_TCW_RELOAD_REG);
+
+ /* Set the mode : */
+ /* - Disable the hardware */
+ /* - Disable the counter mode */
+ /* - Disable the warning */
+ /* - Disable the reset */
+ /* - Disable the timer mode */
+ /* - Enable the counter mode */
+
+ ctrl &= 0xfffc19e2;
+ ctrl |= 0x80000 | (data[4] << 16);
+ outl(ctrl, iobase + ADDI_TCW_CTRL_REG);
+
+ /* Enable or Disable Interrupt */
+ ctrl &= 0xfffff9fd;
+ ctrl |= (data[1] << 1);
+ outl(ctrl, iobase + ADDI_TCW_CTRL_REG);
+
+ /* Set the Up/Down selection */
+ ctrl &= 0xfffbf9ff;
+ ctrl |= (data[6] << 18);
+ outl(ctrl, iobase + ADDI_TCW_CTRL_REG);
+
+ return insn->n;
+}
+
+static int apci1564_counter_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1564_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan);
+ unsigned int ctrl;
+
+ ctrl = inl(iobase + ADDI_TCW_CTRL_REG);
+ switch (data[1]) {
+ case 0: /* Stops the Counter subdevice */
+ ctrl = 0;
+ break;
+ case 1: /* Start the Counter subdevice */
+ ctrl &= 0xfffff9ff;
+ ctrl |= 0x1;
+ break;
+ case 2: /* Clears the Counter subdevice */
+ ctrl &= 0xfffff9ff;
+ ctrl |= 0x400;
+ break;
+ }
+ outl(ctrl, iobase + ADDI_TCW_CTRL_REG);
+
+ return insn->n;
+}
+
+static int apci1564_counter_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1564_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan);
+ unsigned int status;
+
+ /* Read the Counter Actual Value. */
+ data[0] = inl(iobase + ADDI_TCW_VAL_REG);
+
+ status = inl(iobase + ADDI_TCW_STATUS_REG);
+ data[1] = (status >> 1) & 1; /* software trigger status */
+ data[2] = (status >> 2) & 1; /* hardware trigger status */
+ data[3] = (status >> 3) & 1; /* software clear status */
+ data[4] = (status >> 0) & 1; /* overflow status */
+
+ return insn->n;
+}
diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c
new file mode 100644
index 000000000..1f2f78186
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c
@@ -0,0 +1,173 @@
+/* Watchdog Related Defines */
+
+#define ADDIDATA_TIMER 0
+#define ADDIDATA_WATCHDOG 2
+
+/*
+ * (*insn_config) for the timer subdevice
+ *
+ * Configures The Timer, Counter or Watchdog
+ * Data Pointer contains configuration parameters as below
+ * data[0] : 0 Configure As Timer
+ * 1 Configure As Counter
+ * 2 Configure As Watchdog
+ * data[1] : 1 Enable Interrupt
+ * 0 Disable Interrupt
+ * data[2] : Time Unit
+ * data[3] : Reload Value
+ */
+static int apci3501_config_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned int ul_Command1 = 0;
+
+ devpriv->tsk_Current = current;
+ if (data[0] == ADDIDATA_WATCHDOG) {
+
+ devpriv->b_TimerSelectMode = ADDIDATA_WATCHDOG;
+ /* Disable the watchdog */
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+
+ if (data[1] == 1) {
+ /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */
+ outl(0x02, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else {
+ /* disable Timer interrupt */
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ outl(data[2], dev->iobase + APCI3501_TIMER_TIMEBASE_REG);
+ outl(data[3], dev->iobase + APCI3501_TIMER_RELOAD_REG);
+
+ /* Set the mode (e2->e0) */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG) | 0xFFF819E0UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ else if (data[0] == ADDIDATA_TIMER) {
+ /* First Stop The Timer */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ devpriv->b_TimerSelectMode = ADDIDATA_TIMER;
+ if (data[1] == 1) {
+ /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */
+ outl(0x02, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else {
+ /* disable Timer interrupt */
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ outl(data[2], dev->iobase + APCI3501_TIMER_TIMEBASE_REG);
+ outl(data[3], dev->iobase + APCI3501_TIMER_RELOAD_REG);
+
+ /* mode 2 */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 =
+ (ul_Command1 & 0xFFF719E2UL) | 2UL << 13UL | 0x10UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ return insn->n;
+}
+
+/*
+ * (*insn_write) for the timer subdevice
+ *
+ * Start / Stop The Selected Timer , Counter or Watchdog
+ * Data Pointer contains configuration parameters as below
+ * data[0] : 0 Timer
+ * 1 Counter
+ * 2 Watchdog
+ * data[1] : 1 Start
+ * 0 Stop
+ * 2 Trigger
+ */
+static int apci3501_write_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned int ul_Command1 = 0;
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) {
+
+ if (data[1] == 1) {
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL;
+ /* Enable the Watchdog */
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else if (data[1] == 0) { /* Stop The Watchdog */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else if (data[1] == 2) {
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x200UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+ }
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) {
+ if (data[1] == 1) {
+
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL;
+ /* Enable the Timer */
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else if (data[1] == 0) {
+ /* Stop The Timer */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ else if (data[1] == 2) {
+ /* Trigger the Timer */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x200UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+ }
+
+ inl(dev->iobase + APCI3501_TIMER_STATUS_REG);
+ return insn->n;
+}
+
+/*
+ * (*insn_read) for the timer subdevice
+ *
+ * Read The Selected Timer, Counter or Watchdog
+ * Data Pointer contains configuration parameters as below
+ * data[0] : 0 Timer
+ * 1 Counter
+ * 2 Watchdog
+ * data[1] : Timer Counter Watchdog Number
+ */
+static int apci3501_read_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) {
+ data[0] = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1;
+ data[1] = inl(dev->iobase + APCI3501_TIMER_SYNC_REG);
+ }
+
+ else if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) {
+ data[0] = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1;
+ data[1] = inl(dev->iobase + APCI3501_TIMER_SYNC_REG);
+ }
+
+ else if ((devpriv->b_TimerSelectMode != ADDIDATA_TIMER)
+ && (devpriv->b_TimerSelectMode != ADDIDATA_WATCHDOG)) {
+ dev_err(dev->class_dev, "Invalid subdevice.\n");
+ }
+ return insn->n;
+}
diff --git a/drivers/staging/comedi/drivers/addi_apci_1032.c b/drivers/staging/comedi/drivers/addi_apci_1032.c
new file mode 100644
index 000000000..b37166d57
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1032.c
@@ -0,0 +1,394 @@
+/*
+ * addi_apci_1032.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+/*
+ * Driver: addi_apci_1032
+ * Description: ADDI-DATA APCI-1032 Digital Input Board
+ * Author: ADDI-DATA GmbH <info@addi-data.com>,
+ * H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Status: untested
+ * Devices: [ADDI-DATA] APCI-1032 (addi_apci_1032)
+ *
+ * Configuration options:
+ * None; devices are configured automatically.
+ *
+ * This driver models the APCI-1032 as a 32-channel, digital input subdevice
+ * plus an additional digital input subdevice to handle change-of-state (COS)
+ * interrupts (if an interrupt handler can be set up successfully).
+ *
+ * The COS subdevice supports comedi asynchronous read commands.
+ *
+ * Change-Of-State (COS) interrupt configuration:
+ *
+ * Channels 0 to 15 are interruptible. These channels can be configured
+ * to generate interrupts based on AND/OR logic for the desired channels.
+ *
+ * OR logic:
+ * - reacts to rising or falling edges
+ * - interrupt is generated when any enabled channel meets the desired
+ * interrupt condition
+ *
+ * AND logic:
+ * - reacts to changes in level of the selected inputs
+ * - interrupt is generated when all enabled channels meet the desired
+ * interrupt condition
+ * - after an interrupt, a change in level must occur on the selected
+ * inputs to release the IRQ logic
+ *
+ * The COS subdevice must be configured before setting up a comedi
+ * asynchronous command:
+ *
+ * data[0] : INSN_CONFIG_DIGITAL_TRIG
+ * data[1] : trigger number (= 0)
+ * data[2] : configuration operation:
+ * - COMEDI_DIGITAL_TRIG_DISABLE = no interrupts
+ * - COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts
+ * - COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts
+ * data[3] : left-shift for data[4] and data[5]
+ * data[4] : rising-edge/high level channels
+ * data[5] : falling-edge/low level channels
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+#include "amcc_s5933.h"
+
+/*
+ * I/O Register Map
+ */
+#define APCI1032_DI_REG 0x00
+#define APCI1032_MODE1_REG 0x04
+#define APCI1032_MODE2_REG 0x08
+#define APCI1032_STATUS_REG 0x0c
+#define APCI1032_CTRL_REG 0x10
+#define APCI1032_CTRL_INT_OR (0 << 1)
+#define APCI1032_CTRL_INT_AND (1 << 1)
+#define APCI1032_CTRL_INT_ENA (1 << 2)
+
+struct apci1032_private {
+ unsigned long amcc_iobase; /* base of AMCC I/O registers */
+ unsigned int mode1; /* rising-edge/high level channels */
+ unsigned int mode2; /* falling-edge/low level channels */
+ unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */
+};
+
+static int apci1032_reset(struct comedi_device *dev)
+{
+ /* disable the interrupts */
+ outl(0x0, dev->iobase + APCI1032_CTRL_REG);
+ /* Reset the interrupt status register */
+ inl(dev->iobase + APCI1032_STATUS_REG);
+ /* Disable the and/or interrupt */
+ outl(0x0, dev->iobase + APCI1032_MODE1_REG);
+ outl(0x0, dev->iobase + APCI1032_MODE2_REG);
+
+ return 0;
+}
+
+static int apci1032_cos_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1032_private *devpriv = dev->private;
+ unsigned int shift, oldmask;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIGITAL_TRIG:
+ if (data[1] != 0)
+ return -EINVAL;
+ shift = data[3];
+ oldmask = (1U << shift) - 1;
+ switch (data[2]) {
+ case COMEDI_DIGITAL_TRIG_DISABLE:
+ devpriv->ctrl = 0;
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ apci1032_reset(dev);
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
+ if (devpriv->ctrl != (APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_OR)) {
+ /* switching to 'OR' mode */
+ devpriv->ctrl = APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_OR;
+ /* wipe old channels */
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ } else {
+ /* preserve unspecified channels */
+ devpriv->mode1 &= oldmask;
+ devpriv->mode2 &= oldmask;
+ }
+ /* configure specified channels */
+ devpriv->mode1 |= data[4] << shift;
+ devpriv->mode2 |= data[5] << shift;
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
+ if (devpriv->ctrl != (APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_AND)) {
+ /* switching to 'AND' mode */
+ devpriv->ctrl = APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_AND;
+ /* wipe old channels */
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ } else {
+ /* preserve unspecified channels */
+ devpriv->mode1 &= oldmask;
+ devpriv->mode2 &= oldmask;
+ }
+ /* configure specified channels */
+ devpriv->mode1 |= data[4] << shift;
+ devpriv->mode2 |= data[5] << shift;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int apci1032_cos_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = s->state;
+
+ return 0;
+}
+
+static int apci1032_cos_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+/*
+ * Change-Of-State (COS) 'do_cmd' operation
+ *
+ * Enable the COS interrupt as configured by apci1032_cos_insn_config().
+ */
+static int apci1032_cos_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci1032_private *devpriv = dev->private;
+
+ if (!devpriv->ctrl) {
+ dev_warn(dev->class_dev,
+ "Interrupts disabled due to mode configuration!\n");
+ return -EINVAL;
+ }
+
+ outl(devpriv->mode1, dev->iobase + APCI1032_MODE1_REG);
+ outl(devpriv->mode2, dev->iobase + APCI1032_MODE2_REG);
+ outl(devpriv->ctrl, dev->iobase + APCI1032_CTRL_REG);
+
+ return 0;
+}
+
+static int apci1032_cos_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ return apci1032_reset(dev);
+}
+
+static irqreturn_t apci1032_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci1032_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int ctrl;
+
+ /* check interrupt is from this device */
+ if ((inl(devpriv->amcc_iobase + AMCC_OP_REG_INTCSR) &
+ INTCSR_INTR_ASSERTED) == 0)
+ return IRQ_NONE;
+
+ /* check interrupt is enabled */
+ ctrl = inl(dev->iobase + APCI1032_CTRL_REG);
+ if ((ctrl & APCI1032_CTRL_INT_ENA) == 0)
+ return IRQ_HANDLED;
+
+ /* disable the interrupt */
+ outl(ctrl & ~APCI1032_CTRL_INT_ENA, dev->iobase + APCI1032_CTRL_REG);
+
+ s->state = inl(dev->iobase + APCI1032_STATUS_REG) & 0xffff;
+ comedi_buf_write_samples(s, &s->state, 1);
+ comedi_handle_events(dev, s);
+
+ /* enable the interrupt */
+ outl(ctrl, dev->iobase + APCI1032_CTRL_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int apci1032_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI1032_DI_REG);
+
+ return insn->n;
+}
+
+static int apci1032_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct apci1032_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->amcc_iobase = pci_resource_start(pcidev, 0);
+ dev->iobase = pci_resource_start(pcidev, 1);
+ apci1032_reset(dev);
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci1032_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Allocate and Initialise DI Subdevice Structures */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1032_di_insn_bits;
+
+ /* Change-Of-State (COS) interrupt subdevice */
+ s = &dev->subdevices[1];
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = apci1032_cos_insn_config;
+ s->insn_bits = apci1032_cos_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = apci1032_cos_cmdtest;
+ s->do_cmd = apci1032_cos_cmd;
+ s->cancel = apci1032_cos_cancel;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static void apci1032_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci1032_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver apci1032_driver = {
+ .driver_name = "addi_apci_1032",
+ .module = THIS_MODULE,
+ .auto_attach = apci1032_auto_attach,
+ .detach = apci1032_detach,
+};
+
+static int apci1032_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1032_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1032_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1003) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1032_pci_table);
+
+static struct pci_driver apci1032_pci_driver = {
+ .name = "addi_apci_1032",
+ .id_table = apci1032_pci_table,
+ .probe = apci1032_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1032_driver, apci1032_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-1032, 32 channel DI boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_1500.c b/drivers/staging/comedi/drivers/addi_apci_1500.c
new file mode 100644
index 000000000..63991c49f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1500.c
@@ -0,0 +1,878 @@
+/*
+ * addi_apci_1500.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+#include "amcc_s5933.h"
+#include "z8536.h"
+
+/*
+ * PCI Bar 0 Register map (devpriv->amcc)
+ * see amcc_s5933.h for register and bit defines
+ */
+
+/*
+ * PCI Bar 1 Register map (dev->iobase)
+ * see z8536.h for Z8536 internal registers and bit defines
+ */
+#define APCI1500_Z8536_PORTC_REG 0x00
+#define APCI1500_Z8536_PORTB_REG 0x01
+#define APCI1500_Z8536_PORTA_REG 0x02
+#define APCI1500_Z8536_CTRL_REG 0x03
+
+/*
+ * PCI Bar 2 Register map (devpriv->addon)
+ */
+#define APCI1500_CLK_SEL_REG 0x00
+#define APCI1500_DI_REG 0x00
+#define APCI1500_DO_REG 0x02
+
+struct apci1500_private {
+ unsigned long amcc;
+ unsigned long addon;
+
+ unsigned int clk_src;
+
+ /* Digital trigger configuration [0]=AND [1]=OR */
+ unsigned int pm[2]; /* Pattern Mask */
+ unsigned int pt[2]; /* Pattern Transition */
+ unsigned int pp[2]; /* Pattern Polarity */
+};
+
+static unsigned int z8536_read(struct comedi_device *dev, unsigned int reg)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outb(reg, dev->iobase + APCI1500_Z8536_CTRL_REG);
+ val = inb(dev->iobase + APCI1500_Z8536_CTRL_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return val;
+}
+
+static void z8536_write(struct comedi_device *dev,
+ unsigned int val, unsigned int reg)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outb(reg, dev->iobase + APCI1500_Z8536_CTRL_REG);
+ outb(val, dev->iobase + APCI1500_Z8536_CTRL_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static void z8536_reset(struct comedi_device *dev)
+{
+ unsigned long flags;
+
+ /*
+ * Even if the state of the Z8536 is not known, the following
+ * sequence will reset it and put it in State 0.
+ */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ inb(dev->iobase + APCI1500_Z8536_CTRL_REG);
+ outb(0, dev->iobase + APCI1500_Z8536_CTRL_REG);
+ inb(dev->iobase + APCI1500_Z8536_CTRL_REG);
+ outb(0, dev->iobase + APCI1500_Z8536_CTRL_REG);
+ outb(1, dev->iobase + APCI1500_Z8536_CTRL_REG);
+ outb(0, dev->iobase + APCI1500_Z8536_CTRL_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* Disable all Ports and Counter/Timers */
+ z8536_write(dev, 0x00, Z8536_CFG_CTRL_REG);
+
+ /*
+ * Port A is connected to Ditial Input channels 0-7.
+ * Configure the port to allow interrupt detection.
+ */
+ z8536_write(dev, Z8536_PAB_MODE_PTS_BIT |
+ Z8536_PAB_MODE_SB |
+ Z8536_PAB_MODE_PMS_DISABLE,
+ Z8536_PA_MODE_REG);
+ z8536_write(dev, 0xff, Z8536_PB_DPP_REG);
+ z8536_write(dev, 0xff, Z8536_PA_DD_REG);
+
+ /*
+ * Port B is connected to Ditial Input channels 8-13.
+ * Configure the port to allow interrupt detection.
+ *
+ * NOTE: Bits 7 and 6 of Port B are connected to internal
+ * diagnostic signals and bit 7 is inverted.
+ */
+ z8536_write(dev, Z8536_PAB_MODE_PTS_BIT |
+ Z8536_PAB_MODE_SB |
+ Z8536_PAB_MODE_PMS_DISABLE,
+ Z8536_PB_MODE_REG);
+ z8536_write(dev, 0x7f, Z8536_PB_DPP_REG);
+ z8536_write(dev, 0xff, Z8536_PB_DD_REG);
+
+ /*
+ * Not sure what Port C is connected to...
+ */
+ z8536_write(dev, 0x09, Z8536_PC_DPP_REG);
+ z8536_write(dev, 0x0e, Z8536_PC_DD_REG);
+
+ /*
+ * Clear and disable all interrupt sources.
+ *
+ * Just in case, the reset of the Z8536 should have already
+ * done this.
+ */
+ z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_PA_CMDSTAT_REG);
+ z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PA_CMDSTAT_REG);
+
+ z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_PB_CMDSTAT_REG);
+ z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PB_CMDSTAT_REG);
+
+ z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_CT_CMDSTAT_REG(0));
+ z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_CT_CMDSTAT_REG(0));
+
+ z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_CT_CMDSTAT_REG(1));
+ z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_CT_CMDSTAT_REG(1));
+
+ z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_CT_CMDSTAT_REG(2));
+ z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_CT_CMDSTAT_REG(2));
+
+ /* Disable all interrupts */
+ z8536_write(dev, 0x00, Z8536_INT_CTRL_REG);
+}
+
+static void apci1500_port_enable(struct comedi_device *dev, bool enable)
+{
+ unsigned int cfg;
+
+ cfg = z8536_read(dev, Z8536_CFG_CTRL_REG);
+ if (enable)
+ cfg |= (Z8536_CFG_CTRL_PAE | Z8536_CFG_CTRL_PBE);
+ else
+ cfg &= ~(Z8536_CFG_CTRL_PAE | Z8536_CFG_CTRL_PBE);
+ z8536_write(dev, cfg, Z8536_CFG_CTRL_REG);
+}
+
+static void apci1500_timer_enable(struct comedi_device *dev,
+ unsigned int chan, bool enable)
+{
+ unsigned int bit;
+ unsigned int cfg;
+
+ if (chan == 0)
+ bit = Z8536_CFG_CTRL_CT1E;
+ else if (chan == 1)
+ bit = Z8536_CFG_CTRL_CT2E;
+ else
+ bit = Z8536_CFG_CTRL_PCE_CT3E;
+
+ cfg = z8536_read(dev, Z8536_CFG_CTRL_REG);
+ if (enable) {
+ cfg |= bit;
+ } else {
+ cfg &= ~bit;
+ z8536_write(dev, 0x00, Z8536_CT_CMDSTAT_REG(chan));
+ }
+ z8536_write(dev, cfg, Z8536_CFG_CTRL_REG);
+}
+
+static bool apci1500_ack_irq(struct comedi_device *dev,
+ unsigned int reg)
+{
+ unsigned int val;
+
+ val = z8536_read(dev, reg);
+ if ((val & Z8536_STAT_IE_IP) == Z8536_STAT_IE_IP) {
+ val &= 0x0f; /* preserve any write bits */
+ val |= Z8536_CMD_CLR_IP_IUS;
+ z8536_write(dev, val, reg);
+
+ return true;
+ }
+ return false;
+}
+
+static irqreturn_t apci1500_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci1500_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status = 0;
+ unsigned int val;
+
+ val = inl(devpriv->amcc + AMCC_OP_REG_INTCSR);
+ if (!(val & INTCSR_INTR_ASSERTED))
+ return IRQ_NONE;
+
+ if (apci1500_ack_irq(dev, Z8536_PA_CMDSTAT_REG))
+ status |= 0x01; /* port a event (inputs 0-7) */
+
+ if (apci1500_ack_irq(dev, Z8536_PB_CMDSTAT_REG)) {
+ /* Tests if this is an external error */
+ val = inb(dev->iobase + APCI1500_Z8536_PORTB_REG);
+ val &= 0xc0;
+ if (val) {
+ if (val & 0x80) /* voltage error */
+ status |= 0x40;
+ if (val & 0x40) /* short circuit error */
+ status |= 0x80;
+ } else {
+ status |= 0x02; /* port b event (inputs 8-13) */
+ }
+ }
+
+ /*
+ * NOTE: The 'status' returned by the sample matches the
+ * interrupt mask information from the APCI-1500 Users Manual.
+ *
+ * Mask Meaning
+ * ---------- ------------------------------------------
+ * 0x00000001 Event 1 has occurred
+ * 0x00000010 Event 2 has occurred
+ * 0x00000100 Counter/timer 1 has run down (not implemented)
+ * 0x00001000 Counter/timer 2 has run down (not implemented)
+ * 0x00010000 Counter 3 has run down (not implemented)
+ * 0x00100000 Watchdog has run down (not implemented)
+ * 0x01000000 Voltage error
+ * 0x10000000 Short-circuit error
+ */
+ comedi_buf_write_samples(s, &status, 1);
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int apci1500_di_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ /* Disables the main interrupt on the board */
+ z8536_write(dev, 0x00, Z8536_INT_CTRL_REG);
+
+ /* Disable Ports A & B */
+ apci1500_port_enable(dev, false);
+
+ /* Ack any pending interrupts */
+ apci1500_ack_irq(dev, Z8536_PA_CMDSTAT_REG);
+ apci1500_ack_irq(dev, Z8536_PB_CMDSTAT_REG);
+
+ /* Disable pattern interrupts */
+ z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PA_CMDSTAT_REG);
+ z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PB_CMDSTAT_REG);
+
+ /* Enable Ports A & B */
+ apci1500_port_enable(dev, true);
+
+ return 0;
+}
+
+static int apci1500_di_inttrig_start(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct apci1500_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int pa_mode = Z8536_PAB_MODE_PMS_DISABLE;
+ unsigned int pb_mode = Z8536_PAB_MODE_PMS_DISABLE;
+ unsigned int pa_trig = trig_num & 0x01;
+ unsigned int pb_trig = (trig_num >> 1) & 0x01;
+ bool valid_trig = false;
+ unsigned int val;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ /* Disable Ports A & B */
+ apci1500_port_enable(dev, false);
+
+ /* Set Port A for selected trigger pattern */
+ z8536_write(dev, devpriv->pm[pa_trig] & 0xff, Z8536_PA_PM_REG);
+ z8536_write(dev, devpriv->pt[pa_trig] & 0xff, Z8536_PA_PT_REG);
+ z8536_write(dev, devpriv->pp[pa_trig] & 0xff, Z8536_PA_PP_REG);
+
+ /* Set Port B for selected trigger pattern */
+ z8536_write(dev, (devpriv->pm[pb_trig] >> 8) & 0xff, Z8536_PB_PM_REG);
+ z8536_write(dev, (devpriv->pt[pb_trig] >> 8) & 0xff, Z8536_PB_PT_REG);
+ z8536_write(dev, (devpriv->pp[pb_trig] >> 8) & 0xff, Z8536_PB_PP_REG);
+
+ /* Set Port A trigger mode (if enabled) and enable interrupt */
+ if (devpriv->pm[pa_trig] & 0xff) {
+ pa_mode = pa_trig ? Z8536_PAB_MODE_PMS_AND
+ : Z8536_PAB_MODE_PMS_OR;
+
+ val = z8536_read(dev, Z8536_PA_MODE_REG);
+ val &= ~Z8536_PAB_MODE_PMS_MASK;
+ val |= (pa_mode | Z8536_PAB_MODE_IMO);
+ z8536_write(dev, val, Z8536_PA_MODE_REG);
+
+ z8536_write(dev, Z8536_CMD_SET_IE, Z8536_PA_CMDSTAT_REG);
+
+ valid_trig = true;
+
+ dev_dbg(dev->class_dev,
+ "Port A configured for %s mode pattern detection\n",
+ pa_trig ? "AND" : "OR");
+ }
+
+ /* Set Port B trigger mode (if enabled) and enable interrupt */
+ if (devpriv->pm[pb_trig] & 0xff00) {
+ pb_mode = pb_trig ? Z8536_PAB_MODE_PMS_AND
+ : Z8536_PAB_MODE_PMS_OR;
+
+ val = z8536_read(dev, Z8536_PB_MODE_REG);
+ val &= ~Z8536_PAB_MODE_PMS_MASK;
+ val |= (pb_mode | Z8536_PAB_MODE_IMO);
+ z8536_write(dev, val, Z8536_PB_MODE_REG);
+
+ z8536_write(dev, Z8536_CMD_SET_IE, Z8536_PB_CMDSTAT_REG);
+
+ valid_trig = true;
+
+ dev_dbg(dev->class_dev,
+ "Port B configured for %s mode pattern detection\n",
+ pb_trig ? "AND" : "OR");
+ }
+
+ /* Enable Ports A & B */
+ apci1500_port_enable(dev, true);
+
+ if (!valid_trig) {
+ dev_dbg(dev->class_dev,
+ "digital trigger %d is not configured\n", trig_num);
+ return -EINVAL;
+ }
+
+ /* Authorizes the main interrupt on the board */
+ z8536_write(dev, Z8536_INT_CTRL_MIE | Z8536_INT_CTRL_DLC,
+ Z8536_INT_CTRL_REG);
+
+ return 0;
+}
+
+static int apci1500_di_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ s->async->inttrig = apci1500_di_inttrig_start;
+
+ return 0;
+}
+
+static int apci1500_di_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ /*
+ * Internal start source triggers:
+ *
+ * 0 AND mode for Port A (digital inputs 0-7)
+ * AND mode for Port B (digital inputs 8-13 and internal signals)
+ *
+ * 1 OR mode for Port A (digital inputs 0-7)
+ * AND mode for Port B (digital inputs 8-13 and internal signals)
+ *
+ * 2 AND mode for Port A (digital inputs 0-7)
+ * OR mode for Port B (digital inputs 8-13 and internal signals)
+ *
+ * 3 OR mode for Port A (digital inputs 0-7)
+ * OR mode for Port B (digital inputs 8-13 and internal signals)
+ */
+ err |= comedi_check_trigger_arg_max(&cmd->start_arg, 3);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+/*
+ * The pattern-recognition logic must be configured before the digital
+ * input async command is started.
+ *
+ * Digital input channels 0 to 13 can generate interrupts. Channels 14
+ * and 15 are connected to internal board status/diagnostic signals.
+ *
+ * Channel 14 - Voltage error (the external supply is < 5V)
+ * Channel 15 - Short-circuit/overtemperature error
+ *
+ * data[0] : INSN_CONFIG_DIGITAL_TRIG
+ * data[1] : trigger number
+ * 0 = AND mode
+ * 1 = OR mode
+ * data[2] : configuration operation:
+ * COMEDI_DIGITAL_TRIG_DISABLE = no interrupts
+ * COMEDI_DIGITAL_TRIG_ENABLE_EDGES = edge interrupts
+ * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = level interrupts
+ * data[3] : left-shift for data[4] and data[5]
+ * data[4] : rising-edge/high level channels
+ * data[5] : falling-edge/low level channels
+ */
+static int apci1500_di_cfg_trig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1500_private *devpriv = dev->private;
+ unsigned int trig = data[1];
+ unsigned int shift = data[3];
+ unsigned int hi_mask = data[4] << shift;
+ unsigned int lo_mask = data[5] << shift;
+ unsigned int chan_mask = hi_mask | lo_mask;
+ unsigned int old_mask = (1 << shift) - 1;
+ unsigned int pm = devpriv->pm[trig] & old_mask;
+ unsigned int pt = devpriv->pt[trig] & old_mask;
+ unsigned int pp = devpriv->pp[trig] & old_mask;
+
+ if (trig > 1) {
+ dev_dbg(dev->class_dev,
+ "invalid digital trigger number (0=AND, 1=OR)\n");
+ return -EINVAL;
+ }
+
+ if (chan_mask > 0xffff) {
+ dev_dbg(dev->class_dev, "invalid digital trigger channel\n");
+ return -EINVAL;
+ }
+
+ switch (data[2]) {
+ case COMEDI_DIGITAL_TRIG_DISABLE:
+ /* clear trigger configuration */
+ pm = 0;
+ pt = 0;
+ pp = 0;
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
+ pm |= chan_mask; /* enable channels */
+ pt |= chan_mask; /* enable edge detection */
+ pp |= hi_mask; /* rising-edge channels */
+ pp &= ~lo_mask; /* falling-edge channels */
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
+ pm |= chan_mask; /* enable channels */
+ pt &= ~chan_mask; /* enable level detection */
+ pp |= hi_mask; /* high level channels */
+ pp &= ~lo_mask; /* low level channels */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * The AND mode trigger can only have one channel (max) enabled
+ * for edge detection.
+ */
+ if (trig == 0) {
+ int ret = 0;
+ unsigned int src;
+
+ src = pt & 0xff;
+ if (src)
+ ret |= comedi_check_trigger_is_unique(src);
+
+ src = (pt >> 8) & 0xff;
+ if (src)
+ ret |= comedi_check_trigger_is_unique(src);
+
+ if (ret) {
+ dev_dbg(dev->class_dev,
+ "invalid AND trigger configuration\n");
+ return ret;
+ }
+ }
+
+ /* save the trigger configuration */
+ devpriv->pm[trig] = pm;
+ devpriv->pt[trig] = pt;
+ devpriv->pp[trig] = pp;
+
+ return insn->n;
+}
+
+static int apci1500_di_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ switch (data[0]) {
+ case INSN_CONFIG_DIGITAL_TRIG:
+ return apci1500_di_cfg_trig(dev, s, insn, data);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int apci1500_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1500_private *devpriv = dev->private;
+
+ data[1] = inw(devpriv->addon + APCI1500_DI_REG);
+
+ return insn->n;
+}
+
+static int apci1500_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1500_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, devpriv->addon + APCI1500_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci1500_timer_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1500_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ val = data[1] & s->maxdata;
+ z8536_write(dev, val & 0xff, Z8536_CT_RELOAD_LSB_REG(chan));
+ z8536_write(dev, (val >> 8) & 0xff,
+ Z8536_CT_RELOAD_MSB_REG(chan));
+
+ apci1500_timer_enable(dev, chan, true);
+ z8536_write(dev, Z8536_CT_CMDSTAT_GCB,
+ Z8536_CT_CMDSTAT_REG(chan));
+ break;
+ case INSN_CONFIG_DISARM:
+ apci1500_timer_enable(dev, chan, false);
+ break;
+
+ case INSN_CONFIG_GET_COUNTER_STATUS:
+ data[1] = 0;
+ val = z8536_read(dev, Z8536_CT_CMDSTAT_REG(chan));
+ if (val & Z8536_CT_STAT_CIP)
+ data[1] |= COMEDI_COUNTER_COUNTING;
+ if (val & Z8536_CT_CMDSTAT_GCB)
+ data[1] |= COMEDI_COUNTER_ARMED;
+ if (val & Z8536_STAT_IP) {
+ data[1] |= COMEDI_COUNTER_TERMINAL_COUNT;
+ apci1500_ack_irq(dev, Z8536_CT_CMDSTAT_REG(chan));
+ }
+ data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING |
+ COMEDI_COUNTER_TERMINAL_COUNT;
+ break;
+
+ case INSN_CONFIG_SET_COUNTER_MODE:
+ /* Simulate the 8254 timer modes */
+ switch (data[1]) {
+ case I8254_MODE0:
+ /* Interrupt on Terminal Count */
+ val = Z8536_CT_MODE_ECE |
+ Z8536_CT_MODE_DCS_ONESHOT;
+ break;
+ case I8254_MODE1:
+ /* Hardware Retriggerable One-Shot */
+ val = Z8536_CT_MODE_ETE |
+ Z8536_CT_MODE_DCS_ONESHOT;
+ break;
+ case I8254_MODE2:
+ /* Rate Generator */
+ val = Z8536_CT_MODE_CSC |
+ Z8536_CT_MODE_DCS_PULSE;
+ break;
+ case I8254_MODE3:
+ /* Square Wave Mode */
+ val = Z8536_CT_MODE_CSC |
+ Z8536_CT_MODE_DCS_SQRWAVE;
+ break;
+ case I8254_MODE4:
+ /* Software Triggered Strobe */
+ val = Z8536_CT_MODE_REB |
+ Z8536_CT_MODE_DCS_PULSE;
+ break;
+ case I8254_MODE5:
+ /* Hardware Triggered Strobe (watchdog) */
+ val = Z8536_CT_MODE_EOE |
+ Z8536_CT_MODE_ETE |
+ Z8536_CT_MODE_REB |
+ Z8536_CT_MODE_DCS_PULSE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ apci1500_timer_enable(dev, chan, false);
+ z8536_write(dev, val, Z8536_CT_MODE_REG(chan));
+ break;
+
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ if (data[1] > 2)
+ return -EINVAL;
+ devpriv->clk_src = data[1];
+ if (devpriv->clk_src == 2)
+ devpriv->clk_src = 3;
+ outw(devpriv->clk_src, devpriv->addon + APCI1500_CLK_SEL_REG);
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ switch (devpriv->clk_src) {
+ case 0:
+ data[1] = 0; /* 111.86 kHz / 2 */
+ data[2] = 17879; /* 17879 ns (approx) */
+ break;
+ case 1:
+ data[1] = 1; /* 3.49 kHz / 2 */
+ data[2] = 573066; /* 573066 ns (approx) */
+ break;
+ case 3:
+ data[1] = 2; /* 1.747 kHz / 2 */
+ data[2] = 1164822; /* 1164822 ns (approx) */
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case INSN_CONFIG_SET_GATE_SRC:
+ if (chan == 0)
+ return -EINVAL;
+
+ val = z8536_read(dev, Z8536_CT_MODE_REG(chan));
+ val &= Z8536_CT_MODE_EGE;
+ if (data[1] == 1)
+ val |= Z8536_CT_MODE_EGE;
+ else if (data[1] > 1)
+ return -EINVAL;
+ z8536_write(dev, val, Z8536_CT_MODE_REG(chan));
+ break;
+ case INSN_CONFIG_GET_GATE_SRC:
+ if (chan == 0)
+ return -EINVAL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return insn->n;
+}
+
+static int apci1500_timer_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int cmd;
+
+ cmd = z8536_read(dev, Z8536_CT_CMDSTAT_REG(chan));
+ cmd &= Z8536_CT_CMDSTAT_GCB; /* preserve gate */
+ cmd |= Z8536_CT_CMD_TCB; /* set trigger */
+
+ /* software trigger a timer, it only makes sense to do one write */
+ if (insn->n)
+ z8536_write(dev, cmd, Z8536_CT_CMDSTAT_REG(chan));
+
+ return insn->n;
+}
+
+static int apci1500_timer_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int cmd;
+ unsigned int val;
+ int i;
+
+ cmd = z8536_read(dev, Z8536_CT_CMDSTAT_REG(chan));
+ cmd &= Z8536_CT_CMDSTAT_GCB; /* preserve gate */
+ cmd |= Z8536_CT_CMD_RCC; /* set RCC */
+
+ for (i = 0; i < insn->n; i++) {
+ z8536_write(dev, cmd, Z8536_CT_CMDSTAT_REG(chan));
+
+ val = z8536_read(dev, Z8536_CT_VAL_MSB_REG(chan)) << 8;
+ val |= z8536_read(dev, Z8536_CT_VAL_LSB_REG(chan));
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int apci1500_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct apci1500_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->amcc = pci_resource_start(pcidev, 0);
+ devpriv->addon = pci_resource_start(pcidev, 2);
+
+ z8536_reset(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci1500_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1500_di_insn_bits;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->insn_config = apci1500_di_insn_config;
+ s->do_cmdtest = apci1500_di_cmdtest;
+ s->do_cmd = apci1500_di_cmd;
+ s->cancel = apci1500_di_cancel;
+ }
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1500_do_insn_bits;
+
+ /* reset all the digital outputs */
+ outw(0x0, devpriv->addon + APCI1500_DO_REG);
+
+ /* Counter/Timer(Watchdog) subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 3;
+ s->maxdata = 0xffff;
+ s->range_table = &range_unknown;
+ s->insn_config = apci1500_timer_insn_config;
+ s->insn_write = apci1500_timer_insn_write;
+ s->insn_read = apci1500_timer_insn_read;
+
+ /* Enable the PCI interrupt */
+ if (dev->irq) {
+ outl(0x2000 | INTCSR_INBOX_FULL_INT,
+ devpriv->amcc + AMCC_OP_REG_INTCSR);
+ inl(devpriv->amcc + AMCC_OP_REG_IMB1);
+ inl(devpriv->amcc + AMCC_OP_REG_INTCSR);
+ outl(INTCSR_INBOX_INTR_STATUS | 0x2000 | INTCSR_INBOX_FULL_INT,
+ devpriv->amcc + AMCC_OP_REG_INTCSR);
+ }
+
+ return 0;
+}
+
+static void apci1500_detach(struct comedi_device *dev)
+{
+ struct apci1500_private *devpriv = dev->private;
+
+ if (devpriv->amcc)
+ outl(0x0, devpriv->amcc + AMCC_OP_REG_INTCSR);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver apci1500_driver = {
+ .driver_name = "addi_apci_1500",
+ .module = THIS_MODULE,
+ .auto_attach = apci1500_auto_attach,
+ .detach = apci1500_detach,
+};
+
+static int apci1500_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1500_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1500_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMCC, 0x80fc) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1500_pci_table);
+
+static struct pci_driver apci1500_pci_driver = {
+ .name = "addi_apci_1500",
+ .id_table = apci1500_pci_table,
+ .probe = apci1500_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1500_driver, apci1500_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-1500, 16 channel DI / 16 channel DO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_1516.c b/drivers/staging/comedi/drivers/addi_apci_1516.c
new file mode 100644
index 000000000..9c516d1fe
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1516.c
@@ -0,0 +1,225 @@
+/*
+ * addi_apci_1516.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+#include "addi_watchdog.h"
+
+/*
+ * PCI bar 1 I/O Register map - Digital input/output
+ */
+#define APCI1516_DI_REG 0x00
+#define APCI1516_DO_REG 0x04
+
+/*
+ * PCI bar 2 I/O Register map - Watchdog (APCI-1516 and APCI-2016)
+ */
+#define APCI1516_WDOG_REG 0x00
+
+enum apci1516_boardid {
+ BOARD_APCI1016,
+ BOARD_APCI1516,
+ BOARD_APCI2016,
+};
+
+struct apci1516_boardinfo {
+ const char *name;
+ int di_nchan;
+ int do_nchan;
+ int has_wdog;
+};
+
+static const struct apci1516_boardinfo apci1516_boardtypes[] = {
+ [BOARD_APCI1016] = {
+ .name = "apci1016",
+ .di_nchan = 16,
+ },
+ [BOARD_APCI1516] = {
+ .name = "apci1516",
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .has_wdog = 1,
+ },
+ [BOARD_APCI2016] = {
+ .name = "apci2016",
+ .do_nchan = 16,
+ .has_wdog = 1,
+ },
+};
+
+struct apci1516_private {
+ unsigned long wdog_iobase;
+};
+
+static int apci1516_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inw(dev->iobase + APCI1516_DI_REG);
+
+ return insn->n;
+}
+
+static int apci1516_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inw(dev->iobase + APCI1516_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + APCI1516_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci1516_reset(struct comedi_device *dev)
+{
+ const struct apci1516_boardinfo *this_board = dev->board_ptr;
+ struct apci1516_private *devpriv = dev->private;
+
+ if (!this_board->has_wdog)
+ return 0;
+
+ outw(0x0, dev->iobase + APCI1516_DO_REG);
+
+ addi_watchdog_reset(devpriv->wdog_iobase);
+
+ return 0;
+}
+
+static int apci1516_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct apci1516_boardinfo *this_board = NULL;
+ struct apci1516_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(apci1516_boardtypes))
+ this_board = &apci1516_boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->wdog_iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Initialize the digital input subdevice */
+ s = &dev->subdevices[0];
+ if (this_board->di_nchan) {
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = this_board->di_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1516_di_insn_bits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[1];
+ if (this_board->do_nchan) {
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = this_board->do_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1516_do_insn_bits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Initialize the watchdog subdevice */
+ s = &dev->subdevices[2];
+ if (this_board->has_wdog) {
+ ret = addi_watchdog_init(s, devpriv->wdog_iobase);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ apci1516_reset(dev);
+ return 0;
+}
+
+static void apci1516_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci1516_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver apci1516_driver = {
+ .driver_name = "addi_apci_1516",
+ .module = THIS_MODULE,
+ .auto_attach = apci1516_auto_attach,
+ .detach = apci1516_detach,
+};
+
+static int apci1516_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1516_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1516_pci_table[] = {
+ { PCI_VDEVICE(ADDIDATA, 0x1000), BOARD_APCI1016 },
+ { PCI_VDEVICE(ADDIDATA, 0x1001), BOARD_APCI1516 },
+ { PCI_VDEVICE(ADDIDATA, 0x1002), BOARD_APCI2016 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1516_pci_table);
+
+static struct pci_driver apci1516_pci_driver = {
+ .name = "addi_apci_1516",
+ .id_table = apci1516_pci_table,
+ .probe = apci1516_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1516_driver, apci1516_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-1016/1516/2016, 16 channel DIO boards");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_1564.c b/drivers/staging/comedi/drivers/addi_apci_1564.c
new file mode 100644
index 000000000..33e58b9a2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1564.c
@@ -0,0 +1,593 @@
+/*
+ * addi_apci_1564.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include "../comedi_pci.h"
+#include "addi_tcw.h"
+#include "addi_watchdog.h"
+
+/*
+ * PCI BAR 0
+ *
+ * PLD Revision 1.0 I/O Mapping
+ * 0x00 93C76 EEPROM
+ * 0x04 - 0x18 Timer 12-Bit
+ *
+ * PLD Revision 2.x I/O Mapping
+ * 0x00 93C76 EEPROM
+ * 0x04 - 0x14 Digital Input
+ * 0x18 - 0x25 Digital Output
+ * 0x28 - 0x44 Watchdog 8-Bit
+ * 0x48 - 0x64 Timer 12-Bit
+ */
+#define APCI1564_EEPROM_REG 0x00
+#define APCI1564_EEPROM_VCC_STATUS (1 << 8)
+#define APCI1564_EEPROM_TO_REV(x) (((x) >> 4) & 0xf)
+#define APCI1564_EEPROM_DI (1 << 3)
+#define APCI1564_EEPROM_DO (1 << 2)
+#define APCI1564_EEPROM_CS (1 << 1)
+#define APCI1564_EEPROM_CLK (1 << 0)
+#define APCI1564_REV1_TIMER_IOBASE 0x04
+#define APCI1564_REV2_MAIN_IOBASE 0x04
+#define APCI1564_REV2_TIMER_IOBASE 0x48
+
+/*
+ * PCI BAR 1
+ *
+ * PLD Revision 1.0 I/O Mapping
+ * 0x00 - 0x10 Digital Input
+ * 0x14 - 0x20 Digital Output
+ * 0x24 - 0x3c Watchdog 8-Bit
+ *
+ * PLD Revision 2.x I/O Mapping
+ * 0x00 Counter_0
+ * 0x20 Counter_1
+ * 0x30 Counter_3
+ */
+#define APCI1564_REV1_MAIN_IOBASE 0x00
+
+/*
+ * dev->iobase Register Map
+ * PLD Revision 1.0 - PCI BAR 1 + 0x00
+ * PLD Revision 2.x - PCI BAR 0 + 0x04
+ */
+#define APCI1564_DI_REG 0x00
+#define APCI1564_DI_INT_MODE1_REG 0x04
+#define APCI1564_DI_INT_MODE2_REG 0x08
+#define APCI1564_DI_INT_STATUS_REG 0x0c
+#define APCI1564_DI_IRQ_REG 0x10
+#define APCI1564_DO_REG 0x14
+#define APCI1564_DO_INT_CTRL_REG 0x18
+#define APCI1564_DO_INT_STATUS_REG 0x1c
+#define APCI1564_DO_IRQ_REG 0x20
+#define APCI1564_WDOG_REG 0x24
+#define APCI1564_WDOG_RELOAD_REG 0x28
+#define APCI1564_WDOG_TIMEBASE_REG 0x2c
+#define APCI1564_WDOG_CTRL_REG 0x30
+#define APCI1564_WDOG_STATUS_REG 0x34
+#define APCI1564_WDOG_IRQ_REG 0x38
+#define APCI1564_WDOG_WARN_TIMEVAL_REG 0x3c
+#define APCI1564_WDOG_WARN_TIMEBASE_REG 0x40
+
+/*
+ * devpriv->timer Register Map (see addi_tcw.h for register/bit defines)
+ * PLD Revision 1.0 - PCI BAR 0 + 0x04
+ * PLD Revision 2.x - PCI BAR 0 + 0x48
+ */
+
+/*
+ * devpriv->counters Register Map (see addi_tcw.h for register/bit defines)
+ * PLD Revision 2.x - PCI BAR 1 + 0x00
+ */
+#define APCI1564_COUNTER(x) ((x) * 0x20)
+
+struct apci1564_private {
+ unsigned long eeprom; /* base address of EEPROM register */
+ unsigned long timer; /* base address of 12-bit timer */
+ unsigned long counters; /* base address of 32-bit counters */
+ unsigned int mode1; /* riding-edge/high level channels */
+ unsigned int mode2; /* falling-edge/low level channels */
+ unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */
+ struct task_struct *tsk_current;
+};
+
+#include "addi-data/hwdrv_apci1564.c"
+
+static int apci1564_reset(struct comedi_device *dev)
+{
+ struct apci1564_private *devpriv = dev->private;
+
+ /* Disable the input interrupts and reset status register */
+ outl(0x0, dev->iobase + APCI1564_DI_IRQ_REG);
+ inl(dev->iobase + APCI1564_DI_INT_STATUS_REG);
+ outl(0x0, dev->iobase + APCI1564_DI_INT_MODE1_REG);
+ outl(0x0, dev->iobase + APCI1564_DI_INT_MODE2_REG);
+
+ /* Reset the output channels and disable interrupts */
+ outl(0x0, dev->iobase + APCI1564_DO_REG);
+ outl(0x0, dev->iobase + APCI1564_DO_INT_CTRL_REG);
+
+ /* Reset the watchdog registers */
+ addi_watchdog_reset(dev->iobase + APCI1564_WDOG_REG);
+
+ /* Reset the timer registers */
+ outl(0x0, devpriv->timer + ADDI_TCW_CTRL_REG);
+ outl(0x0, devpriv->timer + ADDI_TCW_RELOAD_REG);
+
+ if (devpriv->counters) {
+ unsigned long iobase = devpriv->counters + ADDI_TCW_CTRL_REG;
+
+ /* Reset the counter registers */
+ outl(0x0, iobase + APCI1564_COUNTER(0));
+ outl(0x0, iobase + APCI1564_COUNTER(1));
+ outl(0x0, iobase + APCI1564_COUNTER(2));
+ }
+
+ return 0;
+}
+
+static irqreturn_t apci1564_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci1564_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+ unsigned int ctrl;
+ unsigned int chan;
+
+ status = inl(dev->iobase + APCI1564_DI_IRQ_REG);
+ if (status & APCI1564_DI_INT_ENABLE) {
+ /* disable the interrupt */
+ outl(status & APCI1564_DI_INT_DISABLE,
+ dev->iobase + APCI1564_DI_IRQ_REG);
+
+ s->state = inl(dev->iobase + APCI1564_DI_INT_STATUS_REG) &
+ 0xffff;
+ comedi_buf_write_samples(s, &s->state, 1);
+ comedi_handle_events(dev, s);
+
+ /* enable the interrupt */
+ outl(status, dev->iobase + APCI1564_DI_IRQ_REG);
+ }
+
+ status = inl(devpriv->timer + ADDI_TCW_IRQ_REG);
+ if (status & 0x01) {
+ /* Disable Timer Interrupt */
+ ctrl = inl(devpriv->timer + ADDI_TCW_CTRL_REG);
+ outl(0x0, devpriv->timer + ADDI_TCW_CTRL_REG);
+
+ /* Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_current, 0);
+
+ /* Enable Timer Interrupt */
+ outl(ctrl, devpriv->timer + ADDI_TCW_CTRL_REG);
+ }
+
+ if (devpriv->counters) {
+ for (chan = 0; chan < 4; chan++) {
+ unsigned long iobase;
+
+ iobase = devpriv->counters + APCI1564_COUNTER(chan);
+
+ status = inl(iobase + ADDI_TCW_IRQ_REG);
+ if (status & 0x01) {
+ /* Disable Counter Interrupt */
+ ctrl = inl(iobase + ADDI_TCW_CTRL_REG);
+ outl(0x0, iobase + ADDI_TCW_CTRL_REG);
+
+ /* Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_current, 0);
+
+ /* Enable Counter Interrupt */
+ outl(ctrl, iobase + ADDI_TCW_CTRL_REG);
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int apci1564_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI1564_DI_REG);
+
+ return insn->n;
+}
+
+static int apci1564_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inl(dev->iobase + APCI1564_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + APCI1564_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci1564_diag_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI1564_DO_INT_STATUS_REG) & 3;
+
+ return insn->n;
+}
+
+/*
+ * Change-Of-State (COS) interrupt configuration
+ *
+ * Channels 0 to 15 are interruptible. These channels can be configured
+ * to generate interrupts based on AND/OR logic for the desired channels.
+ *
+ * OR logic
+ * - reacts to rising or falling edges
+ * - interrupt is generated when any enabled channel
+ * meet the desired interrupt condition
+ *
+ * AND logic
+ * - reacts to changes in level of the selected inputs
+ * - interrupt is generated when all enabled channels
+ * meet the desired interrupt condition
+ * - after an interrupt, a change in level must occur on
+ * the selected inputs to release the IRQ logic
+ *
+ * The COS interrupt must be configured before it can be enabled.
+ *
+ * data[0] : INSN_CONFIG_DIGITAL_TRIG
+ * data[1] : trigger number (= 0)
+ * data[2] : configuration operation:
+ * COMEDI_DIGITAL_TRIG_DISABLE = no interrupts
+ * COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts
+ * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts
+ * data[3] : left-shift for data[4] and data[5]
+ * data[4] : rising-edge/high level channels
+ * data[5] : falling-edge/low level channels
+ */
+static int apci1564_cos_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1564_private *devpriv = dev->private;
+ unsigned int shift, oldmask;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIGITAL_TRIG:
+ if (data[1] != 0)
+ return -EINVAL;
+ shift = data[3];
+ oldmask = (1U << shift) - 1;
+ switch (data[2]) {
+ case COMEDI_DIGITAL_TRIG_DISABLE:
+ devpriv->ctrl = 0;
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ outl(0x0, dev->iobase + APCI1564_DI_IRQ_REG);
+ inl(dev->iobase + APCI1564_DI_INT_STATUS_REG);
+ outl(0x0, dev->iobase + APCI1564_DI_INT_MODE1_REG);
+ outl(0x0, dev->iobase + APCI1564_DI_INT_MODE2_REG);
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
+ if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
+ APCI1564_DI_INT_OR)) {
+ /* switching to 'OR' mode */
+ devpriv->ctrl = APCI1564_DI_INT_ENABLE |
+ APCI1564_DI_INT_OR;
+ /* wipe old channels */
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ } else {
+ /* preserve unspecified channels */
+ devpriv->mode1 &= oldmask;
+ devpriv->mode2 &= oldmask;
+ }
+ /* configure specified channels */
+ devpriv->mode1 |= data[4] << shift;
+ devpriv->mode2 |= data[5] << shift;
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
+ if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
+ APCI1564_DI_INT_AND)) {
+ /* switching to 'AND' mode */
+ devpriv->ctrl = APCI1564_DI_INT_ENABLE |
+ APCI1564_DI_INT_AND;
+ /* wipe old channels */
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ } else {
+ /* preserve unspecified channels */
+ devpriv->mode1 &= oldmask;
+ devpriv->mode2 &= oldmask;
+ }
+ /* configure specified channels */
+ devpriv->mode1 |= data[4] << shift;
+ devpriv->mode2 |= data[5] << shift;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return insn->n;
+}
+
+static int apci1564_cos_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = s->state;
+
+ return 0;
+}
+
+static int apci1564_cos_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+/*
+ * Change-Of-State (COS) 'do_cmd' operation
+ *
+ * Enable the COS interrupt as configured by apci1564_cos_insn_config().
+ */
+static int apci1564_cos_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci1564_private *devpriv = dev->private;
+
+ if (!devpriv->ctrl) {
+ dev_warn(dev->class_dev,
+ "Interrupts disabled due to mode configuration!\n");
+ return -EINVAL;
+ }
+
+ outl(devpriv->mode1, dev->iobase + APCI1564_DI_INT_MODE1_REG);
+ outl(devpriv->mode2, dev->iobase + APCI1564_DI_INT_MODE2_REG);
+ outl(devpriv->ctrl, dev->iobase + APCI1564_DI_IRQ_REG);
+
+ return 0;
+}
+
+static int apci1564_cos_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ outl(0x0, dev->iobase + APCI1564_DI_IRQ_REG);
+ inl(dev->iobase + APCI1564_DI_INT_STATUS_REG);
+ outl(0x0, dev->iobase + APCI1564_DI_INT_MODE1_REG);
+ outl(0x0, dev->iobase + APCI1564_DI_INT_MODE2_REG);
+
+ return 0;
+}
+
+static int apci1564_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct apci1564_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int val;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ /* read the EEPROM register and check the I/O map revision */
+ devpriv->eeprom = pci_resource_start(pcidev, 0);
+ val = inl(devpriv->eeprom + APCI1564_EEPROM_REG);
+ if (APCI1564_EEPROM_TO_REV(val) == 0) {
+ /* PLD Revision 1.0 I/O Mapping */
+ dev->iobase = pci_resource_start(pcidev, 1) +
+ APCI1564_REV1_MAIN_IOBASE;
+ devpriv->timer = devpriv->eeprom + APCI1564_REV1_TIMER_IOBASE;
+ } else {
+ /* PLD Revision 2.x I/O Mapping */
+ dev->iobase = devpriv->eeprom + APCI1564_REV2_MAIN_IOBASE;
+ devpriv->timer = devpriv->eeprom + APCI1564_REV2_TIMER_IOBASE;
+ devpriv->counters = pci_resource_start(pcidev, 1);
+ }
+
+ apci1564_reset(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci1564_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 7);
+ if (ret)
+ return ret;
+
+ /* Allocate and Initialise DI Subdevice Structures */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1564_di_insn_bits;
+
+ /* Allocate and Initialise DO Subdevice Structures */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1564_do_insn_bits;
+
+ /* Change-Of-State (COS) interrupt subdevice */
+ s = &dev->subdevices[2];
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->len_chanlist = 1;
+ s->insn_config = apci1564_cos_insn_config;
+ s->insn_bits = apci1564_cos_insn_bits;
+ s->do_cmdtest = apci1564_cos_cmdtest;
+ s->do_cmd = apci1564_cos_cmd;
+ s->cancel = apci1564_cos_cancel;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Timer subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 1;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_digital;
+ s->insn_config = apci1564_timer_insn_config;
+ s->insn_write = apci1564_timer_insn_write;
+ s->insn_read = apci1564_timer_insn_read;
+
+ /* Counter subdevice */
+ s = &dev->subdevices[4];
+ if (devpriv->counters) {
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = 3;
+ s->maxdata = 0xffffffff;
+ s->range_table = &range_digital;
+ s->insn_config = apci1564_counter_insn_config;
+ s->insn_write = apci1564_counter_insn_write;
+ s->insn_read = apci1564_counter_insn_read;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Initialize the watchdog subdevice */
+ s = &dev->subdevices[5];
+ ret = addi_watchdog_init(s, dev->iobase + APCI1564_WDOG_REG);
+ if (ret)
+ return ret;
+
+ /* Initialize the diagnostic status subdevice */
+ s = &dev->subdevices[6];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 2;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1564_diag_insn_bits;
+
+ return 0;
+}
+
+static void apci1564_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci1564_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver apci1564_driver = {
+ .driver_name = "addi_apci_1564",
+ .module = THIS_MODULE,
+ .auto_attach = apci1564_auto_attach,
+ .detach = apci1564_detach,
+};
+
+static int apci1564_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1564_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1564_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1006) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1564_pci_table);
+
+static struct pci_driver apci1564_pci_driver = {
+ .name = "addi_apci_1564",
+ .id_table = apci1564_pci_table,
+ .probe = apci1564_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1564_driver, apci1564_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-1564, 32 channel DI / 32 channel DO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_16xx.c b/drivers/staging/comedi/drivers/addi_apci_16xx.c
new file mode 100644
index 000000000..c63133a12
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_16xx.c
@@ -0,0 +1,187 @@
+/*
+ * addi_apci_16xx.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: S. Weber
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * Register I/O map
+ */
+#define APCI16XX_IN_REG(x) (((x) * 4) + 0x08)
+#define APCI16XX_OUT_REG(x) (((x) * 4) + 0x14)
+#define APCI16XX_DIR_REG(x) (((x) * 4) + 0x20)
+
+enum apci16xx_boardid {
+ BOARD_APCI1648,
+ BOARD_APCI1696,
+};
+
+struct apci16xx_boardinfo {
+ const char *name;
+ int n_chan;
+};
+
+static const struct apci16xx_boardinfo apci16xx_boardtypes[] = {
+ [BOARD_APCI1648] = {
+ .name = "apci1648",
+ .n_chan = 48, /* 2 subdevices */
+ },
+ [BOARD_APCI1696] = {
+ .name = "apci1696",
+ .n_chan = 96, /* 3 subdevices */
+ },
+};
+
+static int apci16xx_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ outl(s->io_bits, dev->iobase + APCI16XX_DIR_REG(s->index));
+
+ return insn->n;
+}
+
+static int apci16xx_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + APCI16XX_OUT_REG(s->index));
+
+ data[1] = inl(dev->iobase + APCI16XX_IN_REG(s->index));
+
+ return insn->n;
+}
+
+static int apci16xx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct apci16xx_boardinfo *board = NULL;
+ struct comedi_subdevice *s;
+ unsigned int n_subdevs;
+ unsigned int last;
+ int i;
+ int ret;
+
+ if (context < ARRAY_SIZE(apci16xx_boardtypes))
+ board = &apci16xx_boardtypes[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ /*
+ * Work out the nubmer of subdevices needed to support all the
+ * digital i/o channels on the board. Each subdevice supports
+ * up to 32 channels.
+ */
+ n_subdevs = board->n_chan / 32;
+ if ((n_subdevs * 32) < board->n_chan) {
+ last = board->n_chan - (n_subdevs * 32);
+ n_subdevs++;
+ } else {
+ last = 0;
+ }
+
+ ret = comedi_alloc_subdevices(dev, n_subdevs);
+ if (ret)
+ return ret;
+
+ /* Initialize the TTL digital i/o subdevices */
+ for (i = 0; i < n_subdevs; i++) {
+ s = &dev->subdevices[i];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = ((i * 32) < board->n_chan) ? 32 : last;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = apci16xx_insn_config;
+ s->insn_bits = apci16xx_dio_insn_bits;
+
+ /* Default all channels to inputs */
+ s->io_bits = 0;
+ outl(s->io_bits, dev->iobase + APCI16XX_DIR_REG(i));
+ }
+
+ return 0;
+}
+
+static struct comedi_driver apci16xx_driver = {
+ .driver_name = "addi_apci_16xx",
+ .module = THIS_MODULE,
+ .auto_attach = apci16xx_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int apci16xx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci16xx_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci16xx_pci_table[] = {
+ { PCI_VDEVICE(ADDIDATA, 0x1009), BOARD_APCI1648 },
+ { PCI_VDEVICE(ADDIDATA, 0x100a), BOARD_APCI1696 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci16xx_pci_table);
+
+static struct pci_driver apci16xx_pci_driver = {
+ .name = "addi_apci_16xx",
+ .id_table = apci16xx_pci_table,
+ .probe = apci16xx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci16xx_driver, apci16xx_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-1648/1696, TTL I/O boards");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_2032.c b/drivers/staging/comedi/drivers/addi_apci_2032.c
new file mode 100644
index 000000000..ad715253b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_2032.c
@@ -0,0 +1,339 @@
+/*
+ * addi_apci_2032.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include "../comedi_pci.h"
+#include "addi_watchdog.h"
+
+/*
+ * PCI bar 1 I/O Register map
+ */
+#define APCI2032_DO_REG 0x00
+#define APCI2032_INT_CTRL_REG 0x04
+#define APCI2032_INT_CTRL_VCC_ENA (1 << 0)
+#define APCI2032_INT_CTRL_CC_ENA (1 << 1)
+#define APCI2032_INT_STATUS_REG 0x08
+#define APCI2032_INT_STATUS_VCC (1 << 0)
+#define APCI2032_INT_STATUS_CC (1 << 1)
+#define APCI2032_STATUS_REG 0x0c
+#define APCI2032_STATUS_IRQ (1 << 0)
+#define APCI2032_WDOG_REG 0x10
+
+struct apci2032_int_private {
+ spinlock_t spinlock;
+ bool active;
+ unsigned char enabled_isns;
+};
+
+static int apci2032_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inl(dev->iobase + APCI2032_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + APCI2032_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci2032_int_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
+ return insn->n;
+}
+
+static void apci2032_int_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci2032_int_private *subpriv = s->private;
+
+ subpriv->active = false;
+ subpriv->enabled_isns = 0;
+ outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
+}
+
+static int apci2032_int_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int apci2032_int_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct apci2032_int_private *subpriv = s->private;
+ unsigned char enabled_isns;
+ unsigned int n;
+ unsigned long flags;
+
+ enabled_isns = 0;
+ for (n = 0; n < cmd->chanlist_len; n++)
+ enabled_isns |= 1 << CR_CHAN(cmd->chanlist[n]);
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+
+ subpriv->enabled_isns = enabled_isns;
+ subpriv->active = true;
+ outl(enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG);
+
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ return 0;
+}
+
+static int apci2032_int_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci2032_int_private *subpriv = s->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ if (subpriv->active)
+ apci2032_int_stop(dev, s);
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ return 0;
+}
+
+static irqreturn_t apci2032_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct apci2032_int_private *subpriv;
+ unsigned int val;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ /* Check if VCC OR CC interrupt has occurred */
+ val = inl(dev->iobase + APCI2032_STATUS_REG) & APCI2032_STATUS_IRQ;
+ if (!val)
+ return IRQ_NONE;
+
+ subpriv = s->private;
+ spin_lock(&subpriv->spinlock);
+
+ val = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
+ /* Disable triggered interrupt sources. */
+ outl(~val & 3, dev->iobase + APCI2032_INT_CTRL_REG);
+ /*
+ * Note: We don't reenable the triggered interrupt sources because they
+ * are level-sensitive, hardware error status interrupt sources and
+ * they'd keep triggering interrupts repeatedly.
+ */
+
+ if (subpriv->active && (val & subpriv->enabled_isns) != 0) {
+ unsigned short bits = 0;
+ int i;
+
+ /* Bits in scan data correspond to indices in channel list. */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (val & (1 << chan))
+ bits |= (1 << i);
+ }
+
+ comedi_buf_write_samples(s, &bits, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+ }
+
+ spin_unlock(&subpriv->spinlock);
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int apci2032_reset(struct comedi_device *dev)
+{
+ outl(0x0, dev->iobase + APCI2032_DO_REG);
+ outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
+
+ addi_watchdog_reset(dev->iobase + APCI2032_WDOG_REG);
+
+ return 0;
+}
+
+static int apci2032_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 1);
+ apci2032_reset(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci2032_interrupt,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2032_do_insn_bits;
+
+ /* Initialize the watchdog subdevice */
+ s = &dev->subdevices[1];
+ ret = addi_watchdog_init(s, dev->iobase + APCI2032_WDOG_REG);
+ if (ret)
+ return ret;
+
+ /* Initialize the interrupt subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 2;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2032_int_insn_bits;
+ if (dev->irq) {
+ struct apci2032_int_private *subpriv;
+
+ dev->read_subdev = s;
+ subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
+ if (!subpriv)
+ return -ENOMEM;
+ spin_lock_init(&subpriv->spinlock);
+ s->private = subpriv;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ | SDF_PACKED;
+ s->len_chanlist = 2;
+ s->do_cmdtest = apci2032_int_cmdtest;
+ s->do_cmd = apci2032_int_cmd;
+ s->cancel = apci2032_int_cancel;
+ }
+
+ return 0;
+}
+
+static void apci2032_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci2032_reset(dev);
+ comedi_pci_detach(dev);
+ if (dev->read_subdev)
+ kfree(dev->read_subdev->private);
+}
+
+static struct comedi_driver apci2032_driver = {
+ .driver_name = "addi_apci_2032",
+ .module = THIS_MODULE,
+ .auto_attach = apci2032_auto_attach,
+ .detach = apci2032_detach,
+};
+
+static int apci2032_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci2032_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci2032_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1004) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci2032_pci_table);
+
+static struct pci_driver apci2032_pci_driver = {
+ .name = "addi_apci_2032",
+ .id_table = apci2032_pci_table,
+ .probe = apci2032_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci2032_driver, apci2032_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-2032, 32 channel DO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_2200.c b/drivers/staging/comedi/drivers/addi_apci_2200.c
new file mode 100644
index 000000000..2b382a52d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_2200.c
@@ -0,0 +1,152 @@
+/*
+ * addi_apci_2200.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+#include "addi_watchdog.h"
+
+/*
+ * I/O Register Map
+ */
+#define APCI2200_DI_REG 0x00
+#define APCI2200_DO_REG 0x04
+#define APCI2200_WDOG_REG 0x08
+
+static int apci2200_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inw(dev->iobase + APCI2200_DI_REG);
+
+ return insn->n;
+}
+
+static int apci2200_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inw(dev->iobase + APCI2200_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + APCI2200_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci2200_reset(struct comedi_device *dev)
+{
+ outw(0x0, dev->iobase + APCI2200_DO_REG);
+
+ addi_watchdog_reset(dev->iobase + APCI2200_WDOG_REG);
+
+ return 0;
+}
+
+static int apci2200_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Initialize the digital input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2200_di_insn_bits;
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2200_do_insn_bits;
+
+ /* Initialize the watchdog subdevice */
+ s = &dev->subdevices[2];
+ ret = addi_watchdog_init(s, dev->iobase + APCI2200_WDOG_REG);
+ if (ret)
+ return ret;
+
+ apci2200_reset(dev);
+ return 0;
+}
+
+static void apci2200_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci2200_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver apci2200_driver = {
+ .driver_name = "addi_apci_2200",
+ .module = THIS_MODULE,
+ .auto_attach = apci2200_auto_attach,
+ .detach = apci2200_detach,
+};
+
+static int apci2200_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci2200_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci2200_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1005) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci2200_pci_table);
+
+static struct pci_driver apci2200_pci_driver = {
+ .name = "addi_apci_2200",
+ .id_table = apci2200_pci_table,
+ .probe = apci2200_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci2200_driver, apci2200_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-2200 Relay board, optically isolated");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_3120.c b/drivers/staging/comedi/drivers/addi_apci_3120.c
new file mode 100644
index 000000000..95dc64bfe
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_3120.c
@@ -0,0 +1,1129 @@
+/*
+ * addi_apci_3120.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+#include "amcc_s5933.h"
+
+/*
+ * PCI BAR 0 register map (devpriv->amcc)
+ * see amcc_s5933.h for register and bit defines
+ */
+#define APCI3120_FIFO_ADVANCE_ON_BYTE_2 (1 << 29)
+
+/*
+ * PCI BAR 1 register map (dev->iobase)
+ */
+#define APCI3120_AI_FIFO_REG 0x00
+#define APCI3120_CTRL_REG 0x00
+#define APCI3120_CTRL_EXT_TRIG (1 << 15)
+#define APCI3120_CTRL_GATE(x) (1 << (12 + (x)))
+#define APCI3120_CTRL_PR(x) (((x) & 0xf) << 8)
+#define APCI3120_CTRL_PA(x) (((x) & 0xf) << 0)
+#define APCI3120_AI_SOFTTRIG_REG 0x02
+#define APCI3120_STATUS_REG 0x02
+#define APCI3120_STATUS_EOC_INT (1 << 15)
+#define APCI3120_STATUS_AMCC_INT (1 << 14)
+#define APCI3120_STATUS_EOS_INT (1 << 13)
+#define APCI3120_STATUS_TIMER2_INT (1 << 12)
+#define APCI3120_STATUS_INT_MASK (0xf << 12)
+#define APCI3120_STATUS_TO_DI_BITS(x) (((x) >> 8) & 0xf)
+#define APCI3120_STATUS_TO_VERSION(x) (((x) >> 4) & 0xf)
+#define APCI3120_STATUS_FIFO_FULL (1 << 2)
+#define APCI3120_STATUS_FIFO_EMPTY (1 << 1)
+#define APCI3120_STATUS_DA_READY (1 << 0)
+#define APCI3120_TIMER_REG 0x04
+#define APCI3120_CHANLIST_REG 0x06
+#define APCI3120_CHANLIST_INDEX(x) (((x) & 0xf) << 8)
+#define APCI3120_CHANLIST_UNIPOLAR (1 << 7)
+#define APCI3120_CHANLIST_GAIN(x) (((x) & 0x3) << 4)
+#define APCI3120_CHANLIST_MUX(x) (((x) & 0xf) << 0)
+#define APCI3120_AO_REG(x) (0x08 + (((x) / 4) * 2))
+#define APCI3120_AO_MUX(x) (((x) & 0x3) << 14)
+#define APCI3120_AO_DATA(x) ((x) << 0)
+#define APCI3120_TIMER_MODE_REG 0x0c
+#define APCI3120_TIMER_MODE(_t, _m) ((_m) << ((_t) * 2))
+#define APCI3120_TIMER_MODE0 0 /* I8254_MODE0 */
+#define APCI3120_TIMER_MODE2 1 /* I8254_MODE2 */
+#define APCI3120_TIMER_MODE4 2 /* I8254_MODE4 */
+#define APCI3120_TIMER_MODE5 3 /* I8254_MODE5 */
+#define APCI3120_TIMER_MODE_MASK(_t) (3 << ((_t) * 2))
+#define APCI3120_CTR0_REG 0x0d
+#define APCI3120_CTR0_DO_BITS(x) ((x) << 4)
+#define APCI3120_CTR0_TIMER_SEL(x) ((x) << 0)
+#define APCI3120_MODE_REG 0x0e
+#define APCI3120_MODE_TIMER2_CLK_OSC (0 << 6)
+#define APCI3120_MODE_TIMER2_CLK_OUT1 (1 << 6)
+#define APCI3120_MODE_TIMER2_CLK_EOC (2 << 6)
+#define APCI3120_MODE_TIMER2_CLK_EOS (3 << 6)
+#define APCI3120_MODE_TIMER2_CLK_MASK (3 << 6)
+#define APCI3120_MODE_TIMER2_AS_TIMER (0 << 4)
+#define APCI3120_MODE_TIMER2_AS_COUNTER (1 << 4)
+#define APCI3120_MODE_TIMER2_AS_WDOG (2 << 4)
+#define APCI3120_MODE_TIMER2_AS_MASK (3 << 4) /* sets AS_TIMER */
+#define APCI3120_MODE_SCAN_ENA (1 << 3)
+#define APCI3120_MODE_TIMER2_IRQ_ENA (1 << 2)
+#define APCI3120_MODE_EOS_IRQ_ENA (1 << 1)
+#define APCI3120_MODE_EOC_IRQ_ENA (1 << 0)
+
+/*
+ * PCI BAR 2 register map (devpriv->addon)
+ */
+#define APCI3120_ADDON_ADDR_REG 0x00
+#define APCI3120_ADDON_DATA_REG 0x02
+#define APCI3120_ADDON_CTRL_REG 0x04
+#define APCI3120_ADDON_CTRL_AMWEN_ENA (1 << 1)
+#define APCI3120_ADDON_CTRL_A2P_FIFO_ENA (1 << 0)
+
+/*
+ * Board revisions
+ */
+#define APCI3120_REVA 0xa
+#define APCI3120_REVB 0xb
+#define APCI3120_REVA_OSC_BASE 70 /* 70ns = 14.29MHz */
+#define APCI3120_REVB_OSC_BASE 50 /* 50ns = 20MHz */
+
+static const struct comedi_lrange apci3120_ai_range = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1)
+ }
+};
+
+enum apci3120_boardid {
+ BOARD_APCI3120,
+ BOARD_APCI3001,
+};
+
+struct apci3120_board {
+ const char *name;
+ unsigned int ai_is_16bit:1;
+ unsigned int has_ao:1;
+};
+
+static const struct apci3120_board apci3120_boardtypes[] = {
+ [BOARD_APCI3120] = {
+ .name = "apci3120",
+ .ai_is_16bit = 1,
+ .has_ao = 1,
+ },
+ [BOARD_APCI3001] = {
+ .name = "apci3001",
+ },
+};
+
+struct apci3120_dmabuf {
+ unsigned short *virt;
+ dma_addr_t hw;
+ unsigned int size;
+ unsigned int use_size;
+};
+
+struct apci3120_private {
+ unsigned long amcc;
+ unsigned long addon;
+ unsigned int osc_base;
+ unsigned int use_dma:1;
+ unsigned int use_double_buffer:1;
+ unsigned int cur_dmabuf:1;
+ struct apci3120_dmabuf dmabuf[2];
+ unsigned char do_bits;
+ unsigned char timer_mode;
+ unsigned char mode;
+ unsigned short ctrl;
+};
+
+static void apci3120_addon_write(struct comedi_device *dev,
+ unsigned int val, unsigned int reg)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ /* 16-bit interface for AMCC add-on registers */
+
+ outw(reg, devpriv->addon + APCI3120_ADDON_ADDR_REG);
+ outw(val & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG);
+
+ outw(reg + 2, devpriv->addon + APCI3120_ADDON_ADDR_REG);
+ outw((val >> 16) & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG);
+}
+
+static void apci3120_init_dma(struct comedi_device *dev,
+ struct apci3120_dmabuf *dmabuf)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ /* AMCC - enable transfer count and reset A2P FIFO */
+ outl(AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO,
+ devpriv->amcc + AMCC_OP_REG_AGCSTS);
+
+ /* Add-On - enable transfer count and reset A2P FIFO */
+ apci3120_addon_write(dev, AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO,
+ AMCC_OP_REG_AGCSTS);
+
+ /* AMCC - enable transfers and reset A2P flags */
+ outl(RESET_A2P_FLAGS | EN_A2P_TRANSFERS,
+ devpriv->amcc + AMCC_OP_REG_MCSR);
+
+ /* Add-On - DMA start address */
+ apci3120_addon_write(dev, dmabuf->hw, AMCC_OP_REG_AMWAR);
+
+ /* Add-On - Number of acquisitions */
+ apci3120_addon_write(dev, dmabuf->use_size, AMCC_OP_REG_AMWTC);
+
+ /* AMCC - enable write complete (DMA) and set FIFO advance */
+ outl(APCI3120_FIFO_ADVANCE_ON_BYTE_2 | AINT_WRITE_COMPL,
+ devpriv->amcc + AMCC_OP_REG_INTCSR);
+
+ /* Add-On - enable DMA */
+ outw(APCI3120_ADDON_CTRL_AMWEN_ENA | APCI3120_ADDON_CTRL_A2P_FIFO_ENA,
+ devpriv->addon + APCI3120_ADDON_CTRL_REG);
+}
+
+static void apci3120_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci3120_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct apci3120_dmabuf *dmabuf0 = &devpriv->dmabuf[0];
+ struct apci3120_dmabuf *dmabuf1 = &devpriv->dmabuf[1];
+ unsigned int dmalen0 = dmabuf0->size;
+ unsigned int dmalen1 = dmabuf1->size;
+ unsigned int scan_bytes;
+
+ scan_bytes = comedi_samples_to_bytes(s, cmd->scan_end_arg);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /*
+ * Must we fill full first buffer? And must we fill
+ * full second buffer when first is once filled?
+ */
+ if (dmalen0 > (cmd->stop_arg * scan_bytes))
+ dmalen0 = cmd->stop_arg * scan_bytes;
+ else if (dmalen1 > (cmd->stop_arg * scan_bytes - dmalen0))
+ dmalen1 = cmd->stop_arg * scan_bytes - dmalen0;
+ }
+
+ if (cmd->flags & CMDF_WAKE_EOS) {
+ /* don't we want wake up every scan? */
+ if (dmalen0 > scan_bytes) {
+ dmalen0 = scan_bytes;
+ if (cmd->scan_end_arg & 1)
+ dmalen0 += 2;
+ }
+ if (dmalen1 > scan_bytes) {
+ dmalen1 = scan_bytes;
+ if (cmd->scan_end_arg & 1)
+ dmalen1 -= 2;
+ if (dmalen1 < 4)
+ dmalen1 = 4;
+ }
+ } else {
+ /* isn't output buff smaller that our DMA buff? */
+ if (dmalen0 > s->async->prealloc_bufsz)
+ dmalen0 = s->async->prealloc_bufsz;
+ if (dmalen1 > s->async->prealloc_bufsz)
+ dmalen1 = s->async->prealloc_bufsz;
+ }
+ dmabuf0->use_size = dmalen0;
+ dmabuf1->use_size = dmalen1;
+
+ apci3120_init_dma(dev, dmabuf0);
+}
+
+/*
+ * There are three timers on the board. They all use the same base
+ * clock with a fixed prescaler for each timer. The base clock used
+ * depends on the board version and type.
+ *
+ * APCI-3120 Rev A boards OSC = 14.29MHz base clock (~70ns)
+ * APCI-3120 Rev B boards OSC = 20MHz base clock (50ns)
+ * APCI-3001 boards OSC = 20MHz base clock (50ns)
+ *
+ * The prescalers for each timer are:
+ * Timer 0 CLK = OSC/10
+ * Timer 1 CLK = OSC/1000
+ * Timer 2 CLK = OSC/1000
+ */
+static unsigned int apci3120_ns_to_timer(struct comedi_device *dev,
+ unsigned int timer,
+ unsigned int ns,
+ unsigned int flags)
+{
+ struct apci3120_private *devpriv = dev->private;
+ unsigned int prescale = (timer == 0) ? 10 : 1000;
+ unsigned int timer_base = devpriv->osc_base * prescale;
+ unsigned int divisor;
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_UP:
+ divisor = DIV_ROUND_UP(ns, timer_base);
+ break;
+ case CMDF_ROUND_DOWN:
+ divisor = ns / timer_base;
+ break;
+ case CMDF_ROUND_NEAREST:
+ default:
+ divisor = DIV_ROUND_CLOSEST(ns, timer_base);
+ break;
+ }
+
+ if (timer == 2) {
+ /* timer 2 is 24-bits */
+ if (divisor > 0x00ffffff)
+ divisor = 0x00ffffff;
+ } else {
+ /* timers 0 and 1 are 16-bits */
+ if (divisor > 0xffff)
+ divisor = 0xffff;
+ }
+ /* the timers require a minimum divisor of 2 */
+ if (divisor < 2)
+ divisor = 2;
+
+ return divisor;
+}
+
+static void apci3120_clr_timer2_interrupt(struct comedi_device *dev)
+{
+ /* a dummy read of APCI3120_CTR0_REG clears the timer 2 interrupt */
+ inb(dev->iobase + APCI3120_CTR0_REG);
+}
+
+static void apci3120_timer_write(struct comedi_device *dev,
+ unsigned int timer, unsigned int val)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ /* write 16-bit value to timer (lower 16-bits of timer 2) */
+ outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
+ APCI3120_CTR0_TIMER_SEL(timer),
+ dev->iobase + APCI3120_CTR0_REG);
+ outw(val & 0xffff, dev->iobase + APCI3120_TIMER_REG);
+
+ if (timer == 2) {
+ /* write upper 16-bits to timer 2 */
+ outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
+ APCI3120_CTR0_TIMER_SEL(timer + 1),
+ dev->iobase + APCI3120_CTR0_REG);
+ outw((val >> 16) & 0xffff, dev->iobase + APCI3120_TIMER_REG);
+ }
+}
+
+static unsigned int apci3120_timer_read(struct comedi_device *dev,
+ unsigned int timer)
+{
+ struct apci3120_private *devpriv = dev->private;
+ unsigned int val;
+
+ /* read 16-bit value from timer (lower 16-bits of timer 2) */
+ outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
+ APCI3120_CTR0_TIMER_SEL(timer),
+ dev->iobase + APCI3120_CTR0_REG);
+ val = inw(dev->iobase + APCI3120_TIMER_REG);
+
+ if (timer == 2) {
+ /* read upper 16-bits from timer 2 */
+ outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
+ APCI3120_CTR0_TIMER_SEL(timer + 1),
+ dev->iobase + APCI3120_CTR0_REG);
+ val |= (inw(dev->iobase + APCI3120_TIMER_REG) << 16);
+ }
+
+ return val;
+}
+
+static void apci3120_timer_set_mode(struct comedi_device *dev,
+ unsigned int timer, unsigned int mode)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ devpriv->timer_mode &= ~APCI3120_TIMER_MODE_MASK(timer);
+ devpriv->timer_mode |= APCI3120_TIMER_MODE(timer, mode);
+ outb(devpriv->timer_mode, dev->iobase + APCI3120_TIMER_MODE_REG);
+}
+
+static void apci3120_timer_enable(struct comedi_device *dev,
+ unsigned int timer, bool enable)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ if (enable)
+ devpriv->ctrl |= APCI3120_CTRL_GATE(timer);
+ else
+ devpriv->ctrl &= ~APCI3120_CTRL_GATE(timer);
+ outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);
+}
+
+static void apci3120_exttrig_enable(struct comedi_device *dev, bool enable)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ if (enable)
+ devpriv->ctrl |= APCI3120_CTRL_EXT_TRIG;
+ else
+ devpriv->ctrl &= ~APCI3120_CTRL_EXT_TRIG;
+ outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);
+}
+
+static void apci3120_set_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ int n_chan, unsigned int *chanlist)
+{
+ struct apci3120_private *devpriv = dev->private;
+ int i;
+
+ /* set chanlist for scan */
+ for (i = 0; i < n_chan; i++) {
+ unsigned int chan = CR_CHAN(chanlist[i]);
+ unsigned int range = CR_RANGE(chanlist[i]);
+ unsigned int val;
+
+ val = APCI3120_CHANLIST_MUX(chan) |
+ APCI3120_CHANLIST_GAIN(range) |
+ APCI3120_CHANLIST_INDEX(i);
+
+ if (comedi_range_is_unipolar(s, range))
+ val |= APCI3120_CHANLIST_UNIPOLAR;
+
+ outw(val, dev->iobase + APCI3120_CHANLIST_REG);
+ }
+
+ /* a dummy read of APCI3120_TIMER_MODE_REG resets the ai FIFO */
+ inw(dev->iobase + APCI3120_TIMER_MODE_REG);
+
+ /* set scan length (PR) and scan start (PA) */
+ devpriv->ctrl = APCI3120_CTRL_PR(n_chan - 1) | APCI3120_CTRL_PA(0);
+ outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);
+
+ /* enable chanlist scanning if necessary */
+ if (n_chan > 1)
+ devpriv->mode |= APCI3120_MODE_SCAN_ENA;
+}
+
+static void apci3120_interrupt_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci3120_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ struct apci3120_dmabuf *dmabuf;
+ unsigned int nbytes;
+ unsigned int nsamples;
+
+ dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf];
+
+ nbytes = dmabuf->use_size - inl(devpriv->amcc + AMCC_OP_REG_MWTC);
+
+ if (nbytes < dmabuf->use_size)
+ dev_err(dev->class_dev, "Interrupted DMA transfer!\n");
+ if (nbytes & 1) {
+ dev_err(dev->class_dev, "Odd count of bytes in DMA ring!\n");
+ async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+
+ nsamples = comedi_bytes_to_samples(s, nbytes);
+ if (nsamples) {
+ comedi_buf_write_samples(s, dmabuf->virt, nsamples);
+
+ if (!(cmd->flags & CMDF_WAKE_EOS))
+ async->events |= COMEDI_CB_EOS;
+ }
+
+ if ((async->events & COMEDI_CB_CANCEL_MASK) ||
+ (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg))
+ return;
+
+ if (devpriv->use_double_buffer) {
+ /* switch DMA buffers for next interrupt */
+ devpriv->cur_dmabuf = !devpriv->cur_dmabuf;
+ dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf];
+ apci3120_init_dma(dev, dmabuf);
+ } else {
+ /* restart DMA if not using double buffering */
+ apci3120_init_dma(dev, dmabuf);
+ }
+}
+
+static irqreturn_t apci3120_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci3120_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int status;
+ unsigned int int_amcc;
+
+ status = inw(dev->iobase + APCI3120_STATUS_REG);
+ int_amcc = inl(devpriv->amcc + AMCC_OP_REG_INTCSR);
+
+ if (!(status & APCI3120_STATUS_INT_MASK) &&
+ !(int_amcc & ANY_S593X_INT)) {
+ dev_err(dev->class_dev, "IRQ from unknown source\n");
+ return IRQ_NONE;
+ }
+
+ outl(int_amcc | AINT_INT_MASK, devpriv->amcc + AMCC_OP_REG_INTCSR);
+
+ if (devpriv->ctrl & APCI3120_CTRL_EXT_TRIG)
+ apci3120_exttrig_enable(dev, false);
+
+ if (int_amcc & MASTER_ABORT_INT)
+ dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n");
+ if (int_amcc & TARGET_ABORT_INT)
+ dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n");
+
+ if ((status & APCI3120_STATUS_EOC_INT) == 0 &&
+ (devpriv->mode & APCI3120_MODE_EOC_IRQ_ENA)) {
+ /* nothing to do... EOC mode is not currently used */
+ }
+
+ if ((status & APCI3120_STATUS_EOS_INT) &&
+ (devpriv->mode & APCI3120_MODE_EOS_IRQ_ENA)) {
+ unsigned short val;
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ val = inw(dev->iobase + APCI3120_AI_FIFO_REG);
+ comedi_buf_write_samples(s, &val, 1);
+ }
+
+ devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA;
+ outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);
+ }
+
+ if (status & APCI3120_STATUS_TIMER2_INT) {
+ /*
+ * for safety...
+ * timer2 interrupts are not enabled in the driver
+ */
+ apci3120_clr_timer2_interrupt(dev);
+ }
+
+ if (status & APCI3120_STATUS_AMCC_INT) {
+ /* AMCC- Clear write complete interrupt (DMA) */
+ outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR);
+
+ /* do some data transfer */
+ apci3120_interrupt_dma(dev, s);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int apci3120_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci3120_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int divisor;
+
+ /* set default mode bits */
+ devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC |
+ APCI3120_MODE_TIMER2_AS_TIMER;
+
+ /* AMCC- Clear write complete interrupt (DMA) */
+ outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR);
+
+ devpriv->cur_dmabuf = 0;
+
+ /* load chanlist for command scan */
+ apci3120_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist);
+
+ if (cmd->start_src == TRIG_EXT)
+ apci3120_exttrig_enable(dev, true);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /*
+ * Timer 1 is used in MODE2 (rate generator) to set the
+ * start time for each scan.
+ */
+ divisor = apci3120_ns_to_timer(dev, 1, cmd->scan_begin_arg,
+ cmd->flags);
+ apci3120_timer_set_mode(dev, 1, APCI3120_TIMER_MODE2);
+ apci3120_timer_write(dev, 1, divisor);
+ }
+
+ /*
+ * Timer 0 is used in MODE2 (rate generator) to set the conversion
+ * time for each acquisition.
+ */
+ divisor = apci3120_ns_to_timer(dev, 0, cmd->convert_arg, cmd->flags);
+ apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE2);
+ apci3120_timer_write(dev, 0, divisor);
+
+ if (devpriv->use_dma)
+ apci3120_setup_dma(dev, s);
+ else
+ devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA;
+
+ /* set mode to enable acquisition */
+ outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);
+
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ apci3120_timer_enable(dev, 1, true);
+ apci3120_timer_enable(dev, 0, true);
+
+ return 0;
+}
+
+static int apci3120_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int arg;
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) { /* Test Delay timing */
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 100000);
+ }
+
+ /* minimum conversion time per sample is 10us */
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* scan begin must be larger than the scan time */
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int apci3120_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ /* Add-On - disable DMA */
+ outw(0, devpriv->addon + 4);
+
+ /* Add-On - disable bus master */
+ apci3120_addon_write(dev, 0, AMCC_OP_REG_AGCSTS);
+
+ /* AMCC - disable bus master */
+ outl(0, devpriv->amcc + AMCC_OP_REG_MCSR);
+
+ /* disable all counters, ext trigger, and reset scan */
+ devpriv->ctrl = 0;
+ outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);
+
+ /* DISABLE_ALL_INTERRUPT */
+ devpriv->mode = 0;
+ outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);
+
+ inw(dev->iobase + APCI3120_STATUS_REG);
+ devpriv->cur_dmabuf = 0;
+
+ return 0;
+}
+
+static int apci3120_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + APCI3120_STATUS_REG);
+ if ((status & APCI3120_STATUS_EOC_INT) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int apci3120_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3120_private *devpriv = dev->private;
+ unsigned int divisor;
+ int ret;
+ int i;
+
+ /* set mode for A/D conversions by software trigger with timer 0 */
+ devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC |
+ APCI3120_MODE_TIMER2_AS_TIMER;
+ outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);
+
+ /* load chanlist for single channel scan */
+ apci3120_set_chanlist(dev, s, 1, &insn->chanspec);
+
+ /*
+ * Timer 0 is used in MODE4 (software triggered strobe) to set the
+ * conversion time for each acquisition. Each conversion is triggered
+ * when the divisor is written to the timer, The conversion is done
+ * when the EOC bit in the status register is '0'.
+ */
+ apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE4);
+ apci3120_timer_enable(dev, 0, true);
+
+ /* fixed conversion time of 10 us */
+ divisor = apci3120_ns_to_timer(dev, 0, 10000, CMDF_ROUND_NEAREST);
+
+ for (i = 0; i < insn->n; i++) {
+ /* trigger conversion */
+ apci3120_timer_write(dev, 0, divisor);
+
+ ret = comedi_timeout(dev, s, insn, apci3120_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = inw(dev->iobase + APCI3120_AI_FIFO_REG);
+ }
+
+ return insn->n;
+}
+
+static int apci3120_ao_ready(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + APCI3120_STATUS_REG);
+ if (status & APCI3120_STATUS_DA_READY)
+ return 0;
+ return -EBUSY;
+}
+
+static int apci3120_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+ int ret;
+
+ ret = comedi_timeout(dev, s, insn, apci3120_ao_ready, 0);
+ if (ret)
+ return ret;
+
+ outw(APCI3120_AO_MUX(chan) | APCI3120_AO_DATA(val),
+ dev->iobase + APCI3120_AO_REG(chan));
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int apci3120_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + APCI3120_STATUS_REG);
+ data[1] = APCI3120_STATUS_TO_DI_BITS(status);
+
+ return insn->n;
+}
+
+static int apci3120_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3120_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data)) {
+ devpriv->do_bits = s->state;
+ outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits),
+ dev->iobase + APCI3120_CTR0_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci3120_timer_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3120_private *devpriv = dev->private;
+ unsigned int divisor;
+ unsigned int status;
+ unsigned int mode;
+ unsigned int timer_mode;
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ apci3120_clr_timer2_interrupt(dev);
+ divisor = apci3120_ns_to_timer(dev, 2, data[1],
+ CMDF_ROUND_DOWN);
+ apci3120_timer_write(dev, 2, divisor);
+ apci3120_timer_enable(dev, 2, true);
+ break;
+
+ case INSN_CONFIG_DISARM:
+ apci3120_timer_enable(dev, 2, false);
+ apci3120_clr_timer2_interrupt(dev);
+ break;
+
+ case INSN_CONFIG_GET_COUNTER_STATUS:
+ data[1] = 0;
+ data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING |
+ COMEDI_COUNTER_TERMINAL_COUNT;
+
+ if (devpriv->ctrl & APCI3120_CTRL_GATE(2)) {
+ data[1] |= COMEDI_COUNTER_ARMED;
+ data[1] |= COMEDI_COUNTER_COUNTING;
+ }
+ status = inw(dev->iobase + APCI3120_STATUS_REG);
+ if (status & APCI3120_STATUS_TIMER2_INT) {
+ data[1] &= ~COMEDI_COUNTER_COUNTING;
+ data[1] |= COMEDI_COUNTER_TERMINAL_COUNT;
+ }
+ break;
+
+ case INSN_CONFIG_SET_COUNTER_MODE:
+ switch (data[1]) {
+ case I8254_MODE0:
+ mode = APCI3120_MODE_TIMER2_AS_COUNTER;
+ timer_mode = APCI3120_TIMER_MODE0;
+ break;
+ case I8254_MODE2:
+ mode = APCI3120_MODE_TIMER2_AS_TIMER;
+ timer_mode = APCI3120_TIMER_MODE2;
+ break;
+ case I8254_MODE4:
+ mode = APCI3120_MODE_TIMER2_AS_TIMER;
+ timer_mode = APCI3120_TIMER_MODE4;
+ break;
+ case I8254_MODE5:
+ mode = APCI3120_MODE_TIMER2_AS_WDOG;
+ timer_mode = APCI3120_TIMER_MODE5;
+ break;
+ default:
+ return -EINVAL;
+ }
+ apci3120_timer_enable(dev, 2, false);
+ apci3120_clr_timer2_interrupt(dev);
+ apci3120_timer_set_mode(dev, 2, timer_mode);
+ devpriv->mode &= ~APCI3120_MODE_TIMER2_AS_MASK;
+ devpriv->mode |= mode;
+ outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int apci3120_timer_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = apci3120_timer_read(dev, 2);
+
+ return insn->n;
+}
+
+static void apci3120_dma_alloc(struct comedi_device *dev)
+{
+ struct apci3120_private *devpriv = dev->private;
+ struct apci3120_dmabuf *dmabuf;
+ int order;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ dmabuf = &devpriv->dmabuf[i];
+ for (order = 2; order >= 0; order--) {
+ dmabuf->virt = dma_alloc_coherent(dev->hw_dev,
+ PAGE_SIZE << order,
+ &dmabuf->hw,
+ GFP_KERNEL);
+ if (dmabuf->virt)
+ break;
+ }
+ if (!dmabuf->virt)
+ break;
+ dmabuf->size = PAGE_SIZE << order;
+
+ if (i == 0)
+ devpriv->use_dma = 1;
+ if (i == 1)
+ devpriv->use_double_buffer = 1;
+ }
+}
+
+static void apci3120_dma_free(struct comedi_device *dev)
+{
+ struct apci3120_private *devpriv = dev->private;
+ struct apci3120_dmabuf *dmabuf;
+ int i;
+
+ if (!devpriv)
+ return;
+
+ for (i = 0; i < 2; i++) {
+ dmabuf = &devpriv->dmabuf[i];
+ if (dmabuf->virt) {
+ dma_free_coherent(dev->hw_dev, dmabuf->size,
+ dmabuf->virt, dmabuf->hw);
+ }
+ }
+}
+
+static void apci3120_reset(struct comedi_device *dev)
+{
+ /* disable all interrupt sources */
+ outb(0, dev->iobase + APCI3120_MODE_REG);
+
+ /* disable all counters, ext trigger, and reset scan */
+ outw(0, dev->iobase + APCI3120_CTRL_REG);
+
+ /* clear interrupt status */
+ inw(dev->iobase + APCI3120_STATUS_REG);
+}
+
+static int apci3120_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct apci3120_board *this_board = NULL;
+ struct apci3120_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int status;
+ int ret;
+
+ if (context < ARRAY_SIZE(apci3120_boardtypes))
+ this_board = &apci3120_boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ pci_set_master(pcidev);
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->amcc = pci_resource_start(pcidev, 0);
+ devpriv->addon = pci_resource_start(pcidev, 2);
+
+ apci3120_reset(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci3120_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = pcidev->irq;
+
+ apci3120_dma_alloc(dev);
+ }
+ }
+
+ status = inw(dev->iobase + APCI3120_STATUS_REG);
+ if (APCI3120_STATUS_TO_VERSION(status) == APCI3120_REVB ||
+ context == BOARD_APCI3001)
+ devpriv->osc_base = APCI3120_REVB_OSC_BASE;
+ else
+ devpriv->osc_base = APCI3120_REVA_OSC_BASE;
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = this_board->ai_is_16bit ? 0xffff : 0x0fff;
+ s->range_table = &apci3120_ai_range;
+ s->insn_read = apci3120_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = apci3120_ai_cmdtest;
+ s->do_cmd = apci3120_ai_cmd;
+ s->cancel = apci3120_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ if (this_board->has_ao) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 8;
+ s->maxdata = 0x3fff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = apci3120_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3120_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3120_do_insn_bits;
+
+ /* Timer subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 1;
+ s->maxdata = 0x00ffffff;
+ s->insn_config = apci3120_timer_insn_config;
+ s->insn_read = apci3120_timer_insn_read;
+
+ return 0;
+}
+
+static void apci3120_detach(struct comedi_device *dev)
+{
+ comedi_pci_detach(dev);
+ apci3120_dma_free(dev);
+}
+
+static struct comedi_driver apci3120_driver = {
+ .driver_name = "addi_apci_3120",
+ .module = THIS_MODULE,
+ .auto_attach = apci3120_auto_attach,
+ .detach = apci3120_detach,
+};
+
+static int apci3120_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci3120_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci3120_pci_table[] = {
+ { PCI_VDEVICE(AMCC, 0x818d), BOARD_APCI3120 },
+ { PCI_VDEVICE(AMCC, 0x828d), BOARD_APCI3001 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci3120_pci_table);
+
+static struct pci_driver apci3120_pci_driver = {
+ .name = "addi_apci_3120",
+ .id_table = apci3120_pci_table,
+ .probe = apci3120_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci3120_driver, apci3120_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-3120, Analog input board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_3501.c b/drivers/staging/comedi/drivers/addi_apci_3501.c
new file mode 100644
index 000000000..73786a3f3
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_3501.c
@@ -0,0 +1,448 @@
+/*
+ * addi_apci_3501.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include "../comedi_pci.h"
+#include "amcc_s5933.h"
+
+/*
+ * PCI bar 1 register I/O map
+ */
+#define APCI3501_AO_CTRL_STATUS_REG 0x00
+#define APCI3501_AO_CTRL_BIPOLAR (1 << 0)
+#define APCI3501_AO_STATUS_READY (1 << 8)
+#define APCI3501_AO_DATA_REG 0x04
+#define APCI3501_AO_DATA_CHAN(x) ((x) << 0)
+#define APCI3501_AO_DATA_VAL(x) ((x) << 8)
+#define APCI3501_AO_DATA_BIPOLAR (1 << 31)
+#define APCI3501_AO_TRIG_SCS_REG 0x08
+#define APCI3501_TIMER_SYNC_REG 0x20
+#define APCI3501_TIMER_RELOAD_REG 0x24
+#define APCI3501_TIMER_TIMEBASE_REG 0x28
+#define APCI3501_TIMER_CTRL_REG 0x2c
+#define APCI3501_TIMER_STATUS_REG 0x30
+#define APCI3501_TIMER_IRQ_REG 0x34
+#define APCI3501_TIMER_WARN_RELOAD_REG 0x38
+#define APCI3501_TIMER_WARN_TIMEBASE_REG 0x3c
+#define APCI3501_DO_REG 0x40
+#define APCI3501_DI_REG 0x50
+
+/*
+ * AMCC S5933 NVRAM
+ */
+#define NVRAM_USER_DATA_START 0x100
+
+#define NVCMD_BEGIN_READ (0x7 << 5)
+#define NVCMD_LOAD_LOW (0x4 << 5)
+#define NVCMD_LOAD_HIGH (0x5 << 5)
+
+/*
+ * Function types stored in the eeprom
+ */
+#define EEPROM_DIGITALINPUT 0
+#define EEPROM_DIGITALOUTPUT 1
+#define EEPROM_ANALOGINPUT 2
+#define EEPROM_ANALOGOUTPUT 3
+#define EEPROM_TIMER 4
+#define EEPROM_WATCHDOG 5
+#define EEPROM_TIMER_WATCHDOG_COUNTER 10
+
+struct apci3501_private {
+ int i_IobaseAmcc;
+ struct task_struct *tsk_Current;
+ unsigned char b_TimerSelectMode;
+};
+
+static struct comedi_lrange apci3501_ao_range = {
+ 2, {
+ BIP_RANGE(10),
+ UNI_RANGE(10)
+ }
+};
+
+static int apci3501_wait_for_dac(struct comedi_device *dev)
+{
+ unsigned int status;
+
+ do {
+ status = inl(dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ } while (!(status & APCI3501_AO_STATUS_READY));
+
+ return 0;
+}
+
+static int apci3501_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int cfg = APCI3501_AO_DATA_CHAN(chan);
+ int ret;
+ int i;
+
+ /*
+ * All analog output channels have the same output range.
+ * 14-bit bipolar: 0-10V
+ * 13-bit unipolar: +/-10V
+ * Changing the range of one channel changes all of them!
+ */
+ if (range) {
+ outl(0, dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ } else {
+ cfg |= APCI3501_AO_DATA_BIPOLAR;
+ outl(APCI3501_AO_CTRL_BIPOLAR,
+ dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ if (range == 1) {
+ if (data[i] > 0x1fff) {
+ dev_err(dev->class_dev,
+ "Unipolar resolution is only 13-bits\n");
+ return -EINVAL;
+ }
+ }
+
+ ret = apci3501_wait_for_dac(dev);
+ if (ret)
+ return ret;
+
+ outl(cfg | APCI3501_AO_DATA_VAL(val),
+ dev->iobase + APCI3501_AO_DATA_REG);
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+#include "addi-data/hwdrv_apci3501.c"
+
+static int apci3501_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI3501_DI_REG) & 0x3;
+
+ return insn->n;
+}
+
+static int apci3501_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inl(dev->iobase + APCI3501_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + APCI3501_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void apci3501_eeprom_wait(unsigned long iobase)
+{
+ unsigned char val;
+
+ do {
+ val = inb(iobase + AMCC_OP_REG_MCSR_NVCMD);
+ } while (val & 0x80);
+}
+
+static unsigned short apci3501_eeprom_readw(unsigned long iobase,
+ unsigned short addr)
+{
+ unsigned short val = 0;
+ unsigned char tmp;
+ unsigned char i;
+
+ /* Add the offset to the start of the user data */
+ addr += NVRAM_USER_DATA_START;
+
+ for (i = 0; i < 2; i++) {
+ /* Load the low 8 bit address */
+ outb(NVCMD_LOAD_LOW, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ apci3501_eeprom_wait(iobase);
+ outb((addr + i) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
+ apci3501_eeprom_wait(iobase);
+
+ /* Load the high 8 bit address */
+ outb(NVCMD_LOAD_HIGH, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ apci3501_eeprom_wait(iobase);
+ outb(((addr + i) >> 8) & 0xff,
+ iobase + AMCC_OP_REG_MCSR_NVDATA);
+ apci3501_eeprom_wait(iobase);
+
+ /* Read the eeprom data byte */
+ outb(NVCMD_BEGIN_READ, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ apci3501_eeprom_wait(iobase);
+ tmp = inb(iobase + AMCC_OP_REG_MCSR_NVDATA);
+ apci3501_eeprom_wait(iobase);
+
+ if (i == 0)
+ val |= tmp;
+ else
+ val |= (tmp << 8);
+ }
+
+ return val;
+}
+
+static int apci3501_eeprom_get_ao_n_chan(struct comedi_device *dev)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned long iobase = devpriv->i_IobaseAmcc;
+ unsigned char nfuncs;
+ int i;
+
+ nfuncs = apci3501_eeprom_readw(iobase, 10) & 0xff;
+
+ /* Read functionality details */
+ for (i = 0; i < nfuncs; i++) {
+ unsigned short offset = i * 4;
+ unsigned short addr;
+ unsigned char func;
+ unsigned short val;
+
+ func = apci3501_eeprom_readw(iobase, 12 + offset) & 0x3f;
+ addr = apci3501_eeprom_readw(iobase, 14 + offset);
+
+ if (func == EEPROM_ANALOGOUTPUT) {
+ val = apci3501_eeprom_readw(iobase, addr + 10);
+ return (val >> 4) & 0x3ff;
+ }
+ }
+ return 0;
+}
+
+static int apci3501_eeprom_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned short addr = CR_CHAN(insn->chanspec);
+
+ data[0] = apci3501_eeprom_readw(devpriv->i_IobaseAmcc, 2 * addr);
+
+ return insn->n;
+}
+
+static irqreturn_t apci3501_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci3501_private *devpriv = dev->private;
+ unsigned int ui_Timer_AOWatchdog;
+ unsigned long ul_Command1;
+
+ /* Disable Interrupt */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FDul;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+
+ ui_Timer_AOWatchdog = inl(dev->iobase + APCI3501_TIMER_IRQ_REG) & 0x1;
+ if ((!ui_Timer_AOWatchdog)) {
+ dev_err(dev->class_dev, "IRQ from unknown source\n");
+ return IRQ_NONE;
+ }
+
+ /* Enable Interrupt Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FDul) | 1 << 1;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ inl(dev->iobase + APCI3501_TIMER_STATUS_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int apci3501_reset(struct comedi_device *dev)
+{
+ unsigned int val;
+ int chan;
+ int ret;
+
+ /* Reset all digital outputs to "0" */
+ outl(0x0, dev->iobase + APCI3501_DO_REG);
+
+ /* Default all analog outputs to 0V (bipolar) */
+ outl(APCI3501_AO_CTRL_BIPOLAR,
+ dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ val = APCI3501_AO_DATA_BIPOLAR | APCI3501_AO_DATA_VAL(0);
+
+ /* Set all analog output channels */
+ for (chan = 0; chan < 8; chan++) {
+ ret = apci3501_wait_for_dac(dev);
+ if (ret) {
+ dev_warn(dev->class_dev,
+ "%s: DAC not-ready for channel %i\n",
+ __func__, chan);
+ } else {
+ outl(val | APCI3501_AO_DATA_CHAN(chan),
+ dev->iobase + APCI3501_AO_DATA_REG);
+ }
+ }
+
+ return 0;
+}
+
+static int apci3501_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct apci3501_private *devpriv;
+ struct comedi_subdevice *s;
+ int ao_n_chan;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0);
+
+ ao_n_chan = apci3501_eeprom_get_ao_n_chan(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci3501_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* Initialize the analog output subdevice */
+ s = &dev->subdevices[0];
+ if (ao_n_chan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = ao_n_chan;
+ s->maxdata = 0x3fff;
+ s->range_table = &apci3501_ao_range;
+ s->insn_write = apci3501_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Initialize the digital input subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 2;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3501_di_insn_bits;
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3501_do_insn_bits;
+
+ /* Initialize the timer/watchdog subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 1;
+ s->maxdata = 0;
+ s->len_chanlist = 1;
+ s->range_table = &range_digital;
+ s->insn_write = apci3501_write_insn_timer;
+ s->insn_read = apci3501_read_insn_timer;
+ s->insn_config = apci3501_config_insn_timer;
+
+ /* Initialize the eeprom subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_MEMORY;
+ s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
+ s->n_chan = 256;
+ s->maxdata = 0xffff;
+ s->insn_read = apci3501_eeprom_insn_read;
+
+ apci3501_reset(dev);
+ return 0;
+}
+
+static void apci3501_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci3501_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver apci3501_driver = {
+ .driver_name = "addi_apci_3501",
+ .module = THIS_MODULE,
+ .auto_attach = apci3501_auto_attach,
+ .detach = apci3501_detach,
+};
+
+static int apci3501_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci3501_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci3501_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3001) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci3501_pci_table);
+
+static struct pci_driver apci3501_pci_driver = {
+ .name = "addi_apci_3501",
+ .id_table = apci3501_pci_table,
+ .probe = apci3501_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci3501_driver, apci3501_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-3501 Analog output board");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_apci_3xxx.c b/drivers/staging/comedi/drivers/addi_apci_3xxx.c
new file mode 100644
index 000000000..bef6efc84
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_3xxx.c
@@ -0,0 +1,968 @@
+/*
+ * addi_apci_3xxx.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: S. Weber
+ *
+ * ADDI-DATA GmbH
+ * Dieselstrasse 3
+ * D-77833 Ottersweier
+ * Tel: +19(0)7223/9493-0
+ * Fax: +49(0)7223/9493-92
+ * http://www.addi-data.com
+ * info@addi-data.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#define CONV_UNIT_NS (1 << 0)
+#define CONV_UNIT_US (1 << 1)
+#define CONV_UNIT_MS (1 << 2)
+
+static const struct comedi_lrange apci3xxx_ai_range = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1)
+ }
+};
+
+static const struct comedi_lrange apci3xxx_ao_range = {
+ 2, {
+ BIP_RANGE(10),
+ UNI_RANGE(10)
+ }
+};
+
+enum apci3xxx_boardid {
+ BOARD_APCI3000_16,
+ BOARD_APCI3000_8,
+ BOARD_APCI3000_4,
+ BOARD_APCI3006_16,
+ BOARD_APCI3006_8,
+ BOARD_APCI3006_4,
+ BOARD_APCI3010_16,
+ BOARD_APCI3010_8,
+ BOARD_APCI3010_4,
+ BOARD_APCI3016_16,
+ BOARD_APCI3016_8,
+ BOARD_APCI3016_4,
+ BOARD_APCI3100_16_4,
+ BOARD_APCI3100_8_4,
+ BOARD_APCI3106_16_4,
+ BOARD_APCI3106_8_4,
+ BOARD_APCI3110_16_4,
+ BOARD_APCI3110_8_4,
+ BOARD_APCI3116_16_4,
+ BOARD_APCI3116_8_4,
+ BOARD_APCI3003,
+ BOARD_APCI3002_16,
+ BOARD_APCI3002_8,
+ BOARD_APCI3002_4,
+ BOARD_APCI3500,
+};
+
+struct apci3xxx_boardinfo {
+ const char *name;
+ int ai_subdev_flags;
+ int ai_n_chan;
+ unsigned int ai_maxdata;
+ unsigned char ai_conv_units;
+ unsigned int ai_min_acq_ns;
+ unsigned int has_ao:1;
+ unsigned int has_dig_in:1;
+ unsigned int has_dig_out:1;
+ unsigned int has_ttl_io:1;
+};
+
+static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = {
+ [BOARD_APCI3000_16] = {
+ .name = "apci3000-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3000_8] = {
+ .name = "apci3000-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3000_4] = {
+ .name = "apci3000-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3006_16] = {
+ .name = "apci3006-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3006_8] = {
+ .name = "apci3006-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3006_4] = {
+ .name = "apci3006-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3010_16] = {
+ .name = "apci3010-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3010_8] = {
+ .name = "apci3010-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3010_4] = {
+ .name = "apci3010-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3016_16] = {
+ .name = "apci3016-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3016_8] = {
+ .name = "apci3016-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3016_4] = {
+ .name = "apci3016-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3100_16_4] = {
+ .name = "apci3100-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3100_8_4] = {
+ .name = "apci3100-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3106_16_4] = {
+ .name = "apci3106-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3106_8_4] = {
+ .name = "apci3106-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3110_16_4] = {
+ .name = "apci3110-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3110_8_4] = {
+ .name = "apci3110-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3116_16_4] = {
+ .name = "apci3116-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3116_8_4] = {
+ .name = "apci3116-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3003] = {
+ .name = "apci3003",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US |
+ CONV_UNIT_NS,
+ .ai_min_acq_ns = 2500,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3002_16] = {
+ .name = "apci3002-16",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3002_8] = {
+ .name = "apci3002-8",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3002_4] = {
+ .name = "apci3002-4",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3500] = {
+ .name = "apci3500",
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+};
+
+struct apci3xxx_private {
+ unsigned int ai_timer;
+ unsigned char ai_time_base;
+};
+
+static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+ unsigned int val;
+
+ /* Test if interrupt occur */
+ status = readl(dev->mmio + 16);
+ if ((status & 0x2) == 0x2) {
+ /* Reset the interrupt */
+ writel(status, dev->mmio + 16);
+
+ val = readl(dev->mmio + 28);
+ comedi_buf_write_samples(s, &val, 1);
+
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static int apci3xxx_ai_started(struct comedi_device *dev)
+{
+ if ((readl(dev->mmio + 8) & 0x80000) == 0x80000)
+ return 1;
+
+ return 0;
+}
+
+static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec)
+{
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned int delay_mode;
+ unsigned int val;
+
+ if (apci3xxx_ai_started(dev))
+ return -EBUSY;
+
+ /* Clear the FIFO */
+ writel(0x10000, dev->mmio + 12);
+
+ /* Get and save the delay mode */
+ delay_mode = readl(dev->mmio + 4);
+ delay_mode &= 0xfffffef0;
+
+ /* Channel configuration selection */
+ writel(delay_mode, dev->mmio + 4);
+
+ /* Make the configuration */
+ val = (range & 3) | ((range >> 2) << 6) |
+ ((aref == AREF_DIFF) << 7);
+ writel(val, dev->mmio + 0);
+
+ /* Channel selection */
+ writel(delay_mode | 0x100, dev->mmio + 4);
+ writel(chan, dev->mmio + 0);
+
+ /* Restore delay mode */
+ writel(delay_mode, dev->mmio + 4);
+
+ /* Set the number of sequence to 1 */
+ writel(1, dev->mmio + 48);
+
+ return 0;
+}
+
+static int apci3xxx_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readl(dev->mmio + 20);
+ if (status & 0x1)
+ return 0;
+ return -EBUSY;
+}
+
+static int apci3xxx_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ int i;
+
+ ret = apci3xxx_ai_setup(dev, insn->chanspec);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Start the conversion */
+ writel(0x80000, dev->mmio + 8);
+
+ /* Wait the EOS */
+ ret = comedi_timeout(dev, s, insn, apci3xxx_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Read the analog value */
+ data[i] = readl(dev->mmio + 28);
+ }
+
+ return insn->n;
+}
+
+static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev,
+ unsigned int *ns, unsigned int flags)
+{
+ const struct apci3xxx_boardinfo *board = dev->board_ptr;
+ struct apci3xxx_private *devpriv = dev->private;
+ unsigned int base;
+ unsigned int timer;
+ int time_base;
+
+ /* time_base: 0 = ns, 1 = us, 2 = ms */
+ for (time_base = 0; time_base < 3; time_base++) {
+ /* skip unsupported time bases */
+ if (!(board->ai_conv_units & (1 << time_base)))
+ continue;
+
+ switch (time_base) {
+ case 0:
+ base = 1;
+ break;
+ case 1:
+ base = 1000;
+ break;
+ case 2:
+ base = 1000000;
+ break;
+ }
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ timer = (*ns + base / 2) / base;
+ break;
+ case CMDF_ROUND_DOWN:
+ timer = *ns / base;
+ break;
+ case CMDF_ROUND_UP:
+ timer = (*ns + base - 1) / base;
+ break;
+ }
+
+ if (timer < 0x10000) {
+ devpriv->ai_time_base = time_base;
+ devpriv->ai_timer = timer;
+ *ns = timer * time_base;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int apci3xxx_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct apci3xxx_boardinfo *board = dev->board_ptr;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_min_acq_ns);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->convert_arg;
+ err |= apci3xxx_ai_ns_to_timer(dev, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int apci3xxx_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ ret = apci3xxx_ai_setup(dev, cmd->chanlist[0]);
+ if (ret)
+ return ret;
+
+ /* Set the convert timing unit */
+ writel(devpriv->ai_time_base, dev->mmio + 36);
+
+ /* Set the convert timing */
+ writel(devpriv->ai_timer, dev->mmio + 32);
+
+ /* Start the conversion */
+ writel(0x180000, dev->mmio + 8);
+
+ return 0;
+}
+
+static int apci3xxx_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ return 0;
+}
+
+static int apci3xxx_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readl(dev->mmio + 96);
+ if (status & 0x100)
+ return 0;
+ return -EBUSY;
+}
+
+static int apci3xxx_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ /* Set the range selection */
+ writel(range, dev->mmio + 96);
+
+ /* Write the analog value to the selected channel */
+ writel((val << 8) | chan, dev->mmio + 100);
+
+ /* Wait the end of transfer */
+ ret = comedi_timeout(dev, s, insn, apci3xxx_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int apci3xxx_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + 32) & 0xf;
+
+ return insn->n;
+}
+
+static int apci3xxx_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inl(dev->iobase + 48) & 0xf;
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + 48);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci3xxx_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask = 0;
+ int ret;
+
+ /*
+ * Port 0 (channels 0-7) are always inputs
+ * Port 1 (channels 8-15) are always outputs
+ * Port 2 (channels 16-23) are programmable i/o
+ */
+ if (data[0] != INSN_CONFIG_DIO_QUERY) {
+ /* ignore all other instructions for ports 0 and 1 */
+ if (chan < 16)
+ return -EINVAL;
+
+ /* changing any channel in port 2 changes the entire port */
+ mask = 0xff0000;
+ }
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ /* update port 2 configuration */
+ outl((s->io_bits >> 24) & 0xff, dev->iobase + 224);
+
+ return insn->n;
+}
+
+static int apci3xxx_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ outl(s->state & 0xff, dev->iobase + 80);
+ if (mask & 0xff0000)
+ outl((s->state >> 16) & 0xff, dev->iobase + 112);
+ }
+
+ val = inl(dev->iobase + 80);
+ val |= (inl(dev->iobase + 64) << 8);
+ if (s->io_bits & 0xff0000)
+ val |= (inl(dev->iobase + 112) << 16);
+ else
+ val |= (inl(dev->iobase + 96) << 16);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int apci3xxx_reset(struct comedi_device *dev)
+{
+ unsigned int val;
+ int i;
+
+ /* Disable the interrupt */
+ disable_irq(dev->irq);
+
+ /* Clear the start command */
+ writel(0, dev->mmio + 8);
+
+ /* Reset the interrupt flags */
+ val = readl(dev->mmio + 16);
+ writel(val, dev->mmio + 16);
+
+ /* clear the EOS */
+ readl(dev->mmio + 20);
+
+ /* Clear the FIFO */
+ for (i = 0; i < 16; i++)
+ val = readl(dev->mmio + 28);
+
+ /* Enable the interrupt */
+ enable_irq(dev->irq);
+
+ return 0;
+}
+
+static int apci3xxx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct apci3xxx_boardinfo *board = NULL;
+ struct apci3xxx_private *devpriv;
+ struct comedi_subdevice *s;
+ int n_subdevices;
+ int subdev;
+ int ret;
+
+ if (context < ARRAY_SIZE(apci3xxx_boardtypes))
+ board = &apci3xxx_boardtypes[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 2);
+ dev->mmio = pci_ioremap_bar(pcidev, 3);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci3xxx_irq_handler,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao +
+ board->has_dig_in + board->has_dig_out +
+ board->has_ttl_io;
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ /* Analog Input subdevice */
+ if (board->ai_n_chan) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | board->ai_subdev_flags;
+ s->n_chan = board->ai_n_chan;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = &apci3xxx_ai_range;
+ s->insn_read = apci3xxx_ai_insn_read;
+ if (dev->irq) {
+ /*
+ * FIXME: The hardware supports multiple scan modes
+ * but the original addi-data driver only supported
+ * reading a single channel with interrupts. Need a
+ * proper datasheet to fix this.
+ *
+ * The following scan modes are supported by the
+ * hardware:
+ * 1) Single software scan
+ * 2) Single hardware triggered scan
+ * 3) Continuous software scan
+ * 4) Continuous software scan with timer delay
+ * 5) Continuous hardware triggered scan
+ * 6) Continuous hardware triggered scan with timer
+ * delay
+ *
+ * For now, limit the chanlist to a single channel.
+ */
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->do_cmdtest = apci3xxx_ai_cmdtest;
+ s->do_cmd = apci3xxx_ai_cmd;
+ s->cancel = apci3xxx_ai_cancel;
+ }
+
+ subdev++;
+ }
+
+ /* Analog Output subdevice */
+ if (board->has_ao) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->range_table = &apci3xxx_ao_range;
+ s->insn_write = apci3xxx_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ subdev++;
+ }
+
+ /* Digital Input subdevice */
+ if (board->has_dig_in) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3xxx_di_insn_bits;
+
+ subdev++;
+ }
+
+ /* Digital Output subdevice */
+ if (board->has_dig_out) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3xxx_do_insn_bits;
+
+ subdev++;
+ }
+
+ /* TTL Digital I/O subdevice */
+ if (board->has_ttl_io) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->io_bits = 0xff; /* channels 0-7 are always outputs */
+ s->range_table = &range_digital;
+ s->insn_config = apci3xxx_dio_insn_config;
+ s->insn_bits = apci3xxx_dio_insn_bits;
+
+ subdev++;
+ }
+
+ apci3xxx_reset(dev);
+ return 0;
+}
+
+static void apci3xxx_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci3xxx_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver apci3xxx_driver = {
+ .driver_name = "addi_apci_3xxx",
+ .module = THIS_MODULE,
+ .auto_attach = apci3xxx_auto_attach,
+ .detach = apci3xxx_detach,
+};
+
+static int apci3xxx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci3xxx_pci_table[] = {
+ { PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 },
+ { PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table);
+
+static struct pci_driver apci3xxx_pci_driver = {
+ .name = "addi_apci_3xxx",
+ .id_table = apci3xxx_pci_table,
+ .probe = apci3xxx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_tcw.h b/drivers/staging/comedi/drivers/addi_tcw.h
new file mode 100644
index 000000000..8794d4cbb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_tcw.h
@@ -0,0 +1,56 @@
+#ifndef _ADDI_TCW_H
+#define _ADDI_TCW_H
+
+/*
+ * Following are the generic definitions for the ADDI-DATA timer/counter/
+ * watchdog (TCW) registers and bits. Some of the registers are not used
+ * depending on the use of the TCW.
+ */
+
+#define ADDI_TCW_VAL_REG 0x00
+
+#define ADDI_TCW_SYNC_REG 0x00
+#define ADDI_TCW_SYNC_CTR_TRIG (1 << 8)
+#define ADDI_TCW_SYNC_CTR_DIS (1 << 7)
+#define ADDI_TCW_SYNC_CTR_ENA (1 << 6)
+#define ADDI_TCW_SYNC_TIMER_TRIG (1 << 5)
+#define ADDI_TCW_SYNC_TIMER_DIS (1 << 4)
+#define ADDI_TCW_SYNC_TIMER_ENA (1 << 3)
+#define ADDI_TCW_SYNC_WDOG_TRIG (1 << 2)
+#define ADDI_TCW_SYNC_WDOG_DIS (1 << 1)
+#define ADDI_TCW_SYNC_WDOG_ENA (1 << 0)
+
+#define ADDI_TCW_RELOAD_REG 0x04
+
+#define ADDI_TCW_TIMEBASE_REG 0x08
+
+#define ADDI_TCW_CTRL_REG 0x0c
+#define ADDI_TCW_CTRL_EXT_CLK_STATUS (1 << 21)
+#define ADDI_TCW_CTRL_CASCADE (1 << 20)
+#define ADDI_TCW_CTRL_CNTR_ENA (1 << 19)
+#define ADDI_TCW_CTRL_CNT_UP (1 << 18)
+#define ADDI_TCW_CTRL_EXT_CLK(x) ((x) << 16)
+#define ADDI_TCW_CTRL_OUT(x) ((x) << 11)
+#define ADDI_TCW_CTRL_GATE (1 << 10)
+#define ADDI_TCW_CTRL_TRIG (1 << 9)
+#define ADDI_TCW_CTRL_EXT_GATE(x) ((x) << 7)
+#define ADDI_TCW_CTRL_EXT_TRIG(x) ((x) << 5)
+#define ADDI_TCW_CTRL_TIMER_ENA (1 << 4)
+#define ADDI_TCW_CTRL_RESET_ENA (1 << 3)
+#define ADDI_TCW_CTRL_WARN_ENA (1 << 2)
+#define ADDI_TCW_CTRL_IRQ_ENA (1 << 1)
+#define ADDI_TCW_CTRL_ENA (1 << 0)
+
+#define ADDI_TCW_STATUS_REG 0x10
+#define ADDI_TCW_STATUS_SOFT_CLR (1 << 3)
+#define ADDI_TCW_STATUS_SOFT_TRIG (1 << 1)
+#define ADDI_TCW_STATUS_OVERFLOW (1 << 0)
+
+#define ADDI_TCW_IRQ_REG 0x14
+#define ADDI_TCW_IRQ (1 << 0)
+
+#define ADDI_TCW_WARN_TIMEVAL_REG 0x18
+
+#define ADDI_TCW_WARN_TIMEBASE_REG 0x1c
+
+#endif
diff --git a/drivers/staging/comedi/drivers/addi_watchdog.c b/drivers/staging/comedi/drivers/addi_watchdog.c
new file mode 100644
index 000000000..9d9853fe5
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_watchdog.c
@@ -0,0 +1,149 @@
+/*
+ * COMEDI driver for the watchdog subdevice found on some addi-data boards
+ * Copyright (c) 2013 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on implementations in various addi-data COMEDI drivers.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+#include "addi_tcw.h"
+#include "addi_watchdog.h"
+
+struct addi_watchdog_private {
+ unsigned long iobase;
+ unsigned int wdog_ctrl;
+};
+
+/*
+ * The watchdog subdevice is configured with two INSN_CONFIG instructions:
+ *
+ * Enable the watchdog and set the reload timeout:
+ * data[0] = INSN_CONFIG_ARM
+ * data[1] = timeout reload value
+ *
+ * Disable the watchdog:
+ * data[0] = INSN_CONFIG_DISARM
+ */
+static int addi_watchdog_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_watchdog_private *spriv = s->private;
+ unsigned int reload;
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ spriv->wdog_ctrl = ADDI_TCW_CTRL_ENA;
+ reload = data[1] & s->maxdata;
+ outl(reload, spriv->iobase + ADDI_TCW_RELOAD_REG);
+
+ /* Time base is 20ms, let the user know the timeout */
+ dev_info(dev->class_dev, "watchdog enabled, timeout:%dms\n",
+ 20 * reload + 20);
+ break;
+ case INSN_CONFIG_DISARM:
+ spriv->wdog_ctrl = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ outl(spriv->wdog_ctrl, spriv->iobase + ADDI_TCW_CTRL_REG);
+
+ return insn->n;
+}
+
+static int addi_watchdog_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_watchdog_private *spriv = s->private;
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = inl(spriv->iobase + ADDI_TCW_STATUS_REG);
+
+ return insn->n;
+}
+
+static int addi_watchdog_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_watchdog_private *spriv = s->private;
+ int i;
+
+ if (spriv->wdog_ctrl == 0) {
+ dev_warn(dev->class_dev, "watchdog is disabled\n");
+ return -EINVAL;
+ }
+
+ /* "ping" the watchdog */
+ for (i = 0; i < insn->n; i++) {
+ outl(spriv->wdog_ctrl | ADDI_TCW_CTRL_TRIG,
+ spriv->iobase + ADDI_TCW_CTRL_REG);
+ }
+
+ return insn->n;
+}
+
+void addi_watchdog_reset(unsigned long iobase)
+{
+ outl(0x0, iobase + ADDI_TCW_CTRL_REG);
+ outl(0x0, iobase + ADDI_TCW_RELOAD_REG);
+}
+EXPORT_SYMBOL_GPL(addi_watchdog_reset);
+
+int addi_watchdog_init(struct comedi_subdevice *s, unsigned long iobase)
+{
+ struct addi_watchdog_private *spriv;
+
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return -ENOMEM;
+
+ spriv->iobase = iobase;
+
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 1;
+ s->maxdata = 0xff;
+ s->insn_config = addi_watchdog_insn_config;
+ s->insn_read = addi_watchdog_insn_read;
+ s->insn_write = addi_watchdog_insn_write;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(addi_watchdog_init);
+
+static int __init addi_watchdog_module_init(void)
+{
+ return 0;
+}
+module_init(addi_watchdog_module_init);
+
+static void __exit addi_watchdog_module_exit(void)
+{
+}
+module_exit(addi_watchdog_module_exit);
+
+MODULE_DESCRIPTION("ADDI-DATA Watchdog subdevice");
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_watchdog.h b/drivers/staging/comedi/drivers/addi_watchdog.h
new file mode 100644
index 000000000..83b47befa
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_watchdog.h
@@ -0,0 +1,9 @@
+#ifndef _ADDI_WATCHDOG_H
+#define _ADDI_WATCHDOG_H
+
+#include "../comedidev.h"
+
+void addi_watchdog_reset(unsigned long iobase);
+int addi_watchdog_init(struct comedi_subdevice *, unsigned long iobase);
+
+#endif
diff --git a/drivers/staging/comedi/drivers/adl_pci6208.c b/drivers/staging/comedi/drivers/adl_pci6208.c
new file mode 100644
index 000000000..7ed3fd6fb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci6208.c
@@ -0,0 +1,211 @@
+/*
+ * adl_pci6208.c
+ * Comedi driver for ADLink 6208 series cards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: adl_pci6208
+ * Description: ADLink PCI-6208/6216 Series Multi-channel Analog Output Cards
+ * Devices: [ADLink] PCI-6208 (adl_pci6208), PCI-6216
+ * Author: nsyeow <nsyeow@pd.jaring.my>
+ * Updated: Wed, 11 Feb 2015 11:37:18 +0000
+ * Status: untested
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ *
+ * All supported devices share the same PCI device ID and are treated as a
+ * PCI-6216 with 16 analog output channels. On a PCI-6208, the upper 8
+ * channels exist in registers, but don't go to DAC chips.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * PCI-6208/6216-GL register map
+ */
+#define PCI6208_AO_CONTROL(x) (0x00 + (2 * (x)))
+#define PCI6208_AO_STATUS 0x00
+#define PCI6208_AO_STATUS_DATA_SEND (1 << 0)
+#define PCI6208_DIO 0x40
+#define PCI6208_DIO_DO_MASK (0x0f)
+#define PCI6208_DIO_DO_SHIFT (0)
+#define PCI6208_DIO_DI_MASK (0xf0)
+#define PCI6208_DIO_DI_SHIFT (4)
+
+static int pci6208_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + PCI6208_AO_STATUS);
+ if ((status & PCI6208_AO_STATUS_DATA_SEND) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci6208_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* D/A transfer rate is 2.2us */
+ ret = comedi_timeout(dev, s, insn, pci6208_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* the hardware expects two's complement values */
+ outw(comedi_offset_munge(s, val),
+ dev->iobase + PCI6208_AO_CONTROL(chan));
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int pci6208_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int val;
+
+ val = inw(dev->iobase + PCI6208_DIO);
+ val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT;
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int pci6208_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI6208_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci6208_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ unsigned int val;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16; /* Only 8 usable on PCI-6208 */
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = pci6208_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[1];
+ /* digital input subdevice */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci6208_di_insn_bits;
+
+ s = &dev->subdevices[2];
+ /* digital output subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci6208_do_insn_bits;
+
+ /*
+ * Get the read back signals from the digital outputs
+ * and save it as the initial state for the subdevice.
+ */
+ val = inw(dev->iobase + PCI6208_DIO);
+ val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT;
+ s->state = val;
+
+ return 0;
+}
+
+static struct comedi_driver adl_pci6208_driver = {
+ .driver_name = "adl_pci6208",
+ .module = THIS_MODULE,
+ .auto_attach = pci6208_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int adl_pci6208_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci6208_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci6208_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x6208) },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050,
+ 0x9999, 0x6208) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table);
+
+static struct pci_driver adl_pci6208_pci_driver = {
+ .name = "adl_pci6208",
+ .id_table = adl_pci6208_pci_table,
+ .probe = adl_pci6208_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for ADLink 6208 series cards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adl_pci7x3x.c b/drivers/staging/comedi/drivers/adl_pci7x3x.c
new file mode 100644
index 000000000..934af3ff7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci7x3x.c
@@ -0,0 +1,275 @@
+/*
+ * COMEDI driver for the ADLINK PCI-723x/743x series boards.
+ * Copyright (C) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on the adl_pci7230 driver written by:
+ * David Fernandez <dfcastelao@gmail.com>
+ * and the adl_pci7432 driver written by:
+ * Michel Lachaine <mike@mikelachaine.ca>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: adl_pci7x3x
+ * Description: 32/64-Channel Isolated Digital I/O Boards
+ * Devices: [ADLink] PCI-7230 (adl_pci7230), PCI-7233 (adl_pci7233),
+ * PCI-7234 (adl_pci7234), PCI-7432 (adl_pci7432), PCI-7433 (adl_pci7433),
+ * PCI-7434 (adl_pci7434)
+ * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Updated: Thu, 02 Aug 2012 14:27:46 -0700
+ * Status: untested
+ *
+ * One or two subdevices are setup by this driver depending on
+ * the number of digital inputs and/or outputs provided by the
+ * board. Each subdevice has a maximum of 32 channels.
+ *
+ * PCI-7230 - 2 subdevices: 0 - 16 input, 1 - 16 output
+ * PCI-7233 - 1 subdevice: 0 - 32 input
+ * PCI-7234 - 1 subdevice: 0 - 32 output
+ * PCI-7432 - 2 subdevices: 0 - 32 input, 1 - 32 output
+ * PCI-7433 - 2 subdevices: 0 - 32 input, 1 - 32 input
+ * PCI-7434 - 2 subdevices: 0 - 32 output, 1 - 32 output
+ *
+ * The PCI-7230, PCI-7432 and PCI-7433 boards also support external
+ * interrupt signals on digital input channels 0 and 1. The PCI-7233
+ * has dual-interrupt sources for change-of-state (COS) on any 16
+ * digital input channels of LSB and for COS on any 16 digital input
+ * lines of MSB. Interrupts are not currently supported by this
+ * driver.
+ *
+ * Configuration Options: not applicable, uses comedi PCI auto config
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * Register I/O map (32-bit access only)
+ */
+#define PCI7X3X_DIO_REG 0x00
+#define PCI743X_DIO_REG 0x04
+
+enum apci1516_boardid {
+ BOARD_PCI7230,
+ BOARD_PCI7233,
+ BOARD_PCI7234,
+ BOARD_PCI7432,
+ BOARD_PCI7433,
+ BOARD_PCI7434,
+};
+
+struct adl_pci7x3x_boardinfo {
+ const char *name;
+ int nsubdevs;
+ int di_nchan;
+ int do_nchan;
+};
+
+static const struct adl_pci7x3x_boardinfo adl_pci7x3x_boards[] = {
+ [BOARD_PCI7230] = {
+ .name = "adl_pci7230",
+ .nsubdevs = 2,
+ .di_nchan = 16,
+ .do_nchan = 16,
+ },
+ [BOARD_PCI7233] = {
+ .name = "adl_pci7233",
+ .nsubdevs = 1,
+ .di_nchan = 32,
+ },
+ [BOARD_PCI7234] = {
+ .name = "adl_pci7234",
+ .nsubdevs = 1,
+ .do_nchan = 32,
+ },
+ [BOARD_PCI7432] = {
+ .name = "adl_pci7432",
+ .nsubdevs = 2,
+ .di_nchan = 32,
+ .do_nchan = 32,
+ },
+ [BOARD_PCI7433] = {
+ .name = "adl_pci7433",
+ .nsubdevs = 2,
+ .di_nchan = 64,
+ },
+ [BOARD_PCI7434] = {
+ .name = "adl_pci7434",
+ .nsubdevs = 2,
+ .do_nchan = 64,
+ }
+};
+
+static int adl_pci7x3x_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long reg = (unsigned long)s->private;
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + reg);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int adl_pci7x3x_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long reg = (unsigned long)s->private;
+
+ data[1] = inl(dev->iobase + reg);
+
+ return insn->n;
+}
+
+static int adl_pci7x3x_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct adl_pci7x3x_boardinfo *board = NULL;
+ struct comedi_subdevice *s;
+ int subdev;
+ int nchan;
+ int ret;
+
+ if (context < ARRAY_SIZE(adl_pci7x3x_boards))
+ board = &adl_pci7x3x_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, board->nsubdevs);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ if (board->di_nchan) {
+ nchan = min(board->di_nchan, 32);
+
+ s = &dev->subdevices[subdev];
+ /* Isolated digital inputs 0 to 15/31 */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_di_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI7X3X_DIO_REG;
+
+ subdev++;
+
+ nchan = board->di_nchan - nchan;
+ if (nchan) {
+ s = &dev->subdevices[subdev];
+ /* Isolated digital inputs 32 to 63 */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_di_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI743X_DIO_REG;
+
+ subdev++;
+ }
+ }
+
+ if (board->do_nchan) {
+ nchan = min(board->do_nchan, 32);
+
+ s = &dev->subdevices[subdev];
+ /* Isolated digital outputs 0 to 15/31 */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_do_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI7X3X_DIO_REG;
+
+ subdev++;
+
+ nchan = board->do_nchan - nchan;
+ if (nchan) {
+ s = &dev->subdevices[subdev];
+ /* Isolated digital outputs 32 to 63 */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_do_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI743X_DIO_REG;
+
+ subdev++;
+ }
+ }
+
+ return 0;
+}
+
+static struct comedi_driver adl_pci7x3x_driver = {
+ .driver_name = "adl_pci7x3x",
+ .module = THIS_MODULE,
+ .auto_attach = adl_pci7x3x_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int adl_pci7x3x_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci7x3x_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci7x3x_pci_table[] = {
+ { PCI_VDEVICE(ADLINK, 0x7230), BOARD_PCI7230 },
+ { PCI_VDEVICE(ADLINK, 0x7233), BOARD_PCI7233 },
+ { PCI_VDEVICE(ADLINK, 0x7234), BOARD_PCI7234 },
+ { PCI_VDEVICE(ADLINK, 0x7432), BOARD_PCI7432 },
+ { PCI_VDEVICE(ADLINK, 0x7433), BOARD_PCI7433 },
+ { PCI_VDEVICE(ADLINK, 0x7434), BOARD_PCI7434 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci7x3x_pci_table);
+
+static struct pci_driver adl_pci7x3x_pci_driver = {
+ .name = "adl_pci7x3x",
+ .id_table = adl_pci7x3x_pci_table,
+ .probe = adl_pci7x3x_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci7x3x_driver, adl_pci7x3x_pci_driver);
+
+MODULE_DESCRIPTION("ADLINK PCI-723x/743x Isolated Digital I/O boards");
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adl_pci8164.c b/drivers/staging/comedi/drivers/adl_pci8164.c
new file mode 100644
index 000000000..da901c8de
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci8164.c
@@ -0,0 +1,163 @@
+/*
+ * comedi/drivers/adl_pci8164.c
+ *
+ * Hardware comedi driver for PCI-8164 Adlink card
+ * Copyright (C) 2004 Michel Lachine <mike@mikelachaine.ca>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: adl_pci8164
+ * Description: Driver for the Adlink PCI-8164 4 Axes Motion Control board
+ * Devices: [ADLink] PCI-8164 (adl_pci8164)
+ * Author: Michel Lachaine <mike@mikelachaine.ca>
+ * Status: experimental
+ * Updated: Mon, 14 Apr 2008 15:10:32 +0100
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+#define PCI8164_AXIS(x) ((x) * 0x08)
+#define PCI8164_CMD_MSTS_REG 0x00
+#define PCI8164_OTP_SSTS_REG 0x02
+#define PCI8164_BUF0_REG 0x04
+#define PCI8164_BUF1_REG 0x06
+
+static int adl_pci8164_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long offset = (unsigned long)s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = inw(dev->iobase + PCI8164_AXIS(chan) + offset);
+
+ return insn->n;
+}
+
+static int adl_pci8164_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long offset = (unsigned long)s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ outw(data[i], dev->iobase + PCI8164_AXIS(chan) + offset);
+
+ return insn->n;
+}
+
+static int adl_pci8164_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* read MSTS register / write CMD register for each axis (channel) */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_PROC;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 4;
+ s->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_CMD_MSTS_REG;
+
+ /* read SSTS register / write OTP register for each axis (channel) */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_PROC;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 4;
+ s->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_OTP_SSTS_REG;
+
+ /* read/write BUF0 register for each axis (channel) */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_PROC;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 4;
+ s->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_BUF0_REG;
+
+ /* read/write BUF1 register for each axis (channel) */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_PROC;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 4;
+ s->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_BUF1_REG;
+
+ return 0;
+}
+
+static struct comedi_driver adl_pci8164_driver = {
+ .driver_name = "adl_pci8164",
+ .module = THIS_MODULE,
+ .auto_attach = adl_pci8164_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int adl_pci8164_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci8164_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci8164_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x8164) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci8164_pci_table);
+
+static struct pci_driver adl_pci8164_pci_driver = {
+ .name = "adl_pci8164",
+ .id_table = adl_pci8164_pci_table,
+ .probe = adl_pci8164_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci8164_driver, adl_pci8164_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adl_pci9111.c b/drivers/staging/comedi/drivers/adl_pci9111.c
new file mode 100644
index 000000000..c9df3afe9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci9111.c
@@ -0,0 +1,775 @@
+/*
+
+comedi/drivers/adl_pci9111.c
+
+Hardware driver for PCI9111 ADLink cards:
+
+PCI-9111HR
+
+Copyright (C) 2002-2005 Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+*/
+
+/*
+Driver: adl_pci9111
+Description: Adlink PCI-9111HR
+Author: Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr>
+Devices: [ADLink] PCI-9111HR (adl_pci9111)
+Status: experimental
+
+Supports:
+
+ - ai_insn read
+ - ao_insn read/write
+ - di_insn read
+ - do_insn read/write
+ - ai_do_cmd mode with the following sources:
+
+ - start_src TRIG_NOW
+ - scan_begin_src TRIG_FOLLOW TRIG_TIMER TRIG_EXT
+ - convert_src TRIG_TIMER TRIG_EXT
+ - scan_end_src TRIG_COUNT
+ - stop_src TRIG_COUNT TRIG_NONE
+
+The scanned channels must be consecutive and start from 0. They must
+all have the same range and aref.
+
+Configuration options: not applicable, uses PCI auto config
+*/
+
+/*
+CHANGELOG:
+
+2005/02/17 Extend AI streaming capabilities. Now, scan_begin_arg can be
+a multiple of chanlist_len*convert_arg.
+2002/02/19 Fixed the two's complement conversion in pci9111_(hr_)ai_get_data.
+2002/02/18 Added external trigger support for analog input.
+
+TODO:
+
+ - Really test implemented functionality.
+ - Add support for the PCI-9111DG with a probe routine to identify
+ the card type (perhaps with the help of the channel number readback
+ of the A/D Data register).
+ - Add external multiplexer support.
+
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "plx9052.h"
+#include "comedi_8254.h"
+
+#define PCI9111_FIFO_HALF_SIZE 512
+
+#define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS 10000
+
+#define PCI9111_RANGE_SETTING_DELAY 10
+#define PCI9111_AI_INSTANT_READ_UDELAY_US 2
+
+/*
+ * IO address map and bit defines
+ */
+#define PCI9111_AI_FIFO_REG 0x00
+#define PCI9111_AO_REG 0x00
+#define PCI9111_DIO_REG 0x02
+#define PCI9111_EDIO_REG 0x04
+#define PCI9111_AI_CHANNEL_REG 0x06
+#define PCI9111_AI_RANGE_STAT_REG 0x08
+#define PCI9111_AI_STAT_AD_BUSY (1 << 7)
+#define PCI9111_AI_STAT_FF_FF (1 << 6)
+#define PCI9111_AI_STAT_FF_HF (1 << 5)
+#define PCI9111_AI_STAT_FF_EF (1 << 4)
+#define PCI9111_AI_RANGE_MASK (7 << 0)
+#define PCI9111_AI_TRIG_CTRL_REG 0x0a
+#define PCI9111_AI_TRIG_CTRL_TRGEVENT (1 << 5)
+#define PCI9111_AI_TRIG_CTRL_POTRG (1 << 4)
+#define PCI9111_AI_TRIG_CTRL_PTRG (1 << 3)
+#define PCI9111_AI_TRIG_CTRL_ETIS (1 << 2)
+#define PCI9111_AI_TRIG_CTRL_TPST (1 << 1)
+#define PCI9111_AI_TRIG_CTRL_ASCAN (1 << 0)
+#define PCI9111_INT_CTRL_REG 0x0c
+#define PCI9111_INT_CTRL_ISC2 (1 << 3)
+#define PCI9111_INT_CTRL_FFEN (1 << 2)
+#define PCI9111_INT_CTRL_ISC1 (1 << 1)
+#define PCI9111_INT_CTRL_ISC0 (1 << 0)
+#define PCI9111_SOFT_TRIG_REG 0x0e
+#define PCI9111_8254_BASE_REG 0x40
+#define PCI9111_INT_CLR_REG 0x48
+
+/* PLX 9052 Local Interrupt 1 enabled and active */
+#define PCI9111_LI1_ACTIVE (PLX9052_INTCSR_LI1ENAB | \
+ PLX9052_INTCSR_LI1STAT)
+
+/* PLX 9052 Local Interrupt 2 enabled and active */
+#define PCI9111_LI2_ACTIVE (PLX9052_INTCSR_LI2ENAB | \
+ PLX9052_INTCSR_LI2STAT)
+
+static const struct comedi_lrange pci9111_ai_range = {
+ 5, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+struct pci9111_private_data {
+ unsigned long lcr_io_base;
+
+ unsigned int scan_delay;
+ unsigned int chunk_counter;
+ unsigned int chunk_num_samples;
+
+ unsigned short ai_bounce_buffer[2 * PCI9111_FIFO_HALF_SIZE];
+};
+
+static void plx9050_interrupt_control(unsigned long io_base,
+ bool LINTi1_enable,
+ bool LINTi1_active_high,
+ bool LINTi2_enable,
+ bool LINTi2_active_high,
+ bool interrupt_enable)
+{
+ int flags = 0;
+
+ if (LINTi1_enable)
+ flags |= PLX9052_INTCSR_LI1ENAB;
+ if (LINTi1_active_high)
+ flags |= PLX9052_INTCSR_LI1POL;
+ if (LINTi2_enable)
+ flags |= PLX9052_INTCSR_LI2ENAB;
+ if (LINTi2_active_high)
+ flags |= PLX9052_INTCSR_LI2POL;
+
+ if (interrupt_enable)
+ flags |= PLX9052_INTCSR_PCIENAB;
+
+ outb(flags, io_base + PLX9052_INTCSR);
+}
+
+enum pci9111_ISC0_sources {
+ irq_on_eoc,
+ irq_on_fifo_half_full
+};
+
+enum pci9111_ISC1_sources {
+ irq_on_timer_tick,
+ irq_on_external_trigger
+};
+
+static void pci9111_interrupt_source_set(struct comedi_device *dev,
+ enum pci9111_ISC0_sources irq_0_source,
+ enum pci9111_ISC1_sources irq_1_source)
+{
+ int flags;
+
+ /* Read the current interrupt control bits */
+ flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+ /* Shift the bits so they are compatible with the write register */
+ flags >>= 4;
+ /* Mask off the ISCx bits */
+ flags &= 0xc0;
+
+ /* Now set the new ISCx bits */
+ if (irq_0_source == irq_on_fifo_half_full)
+ flags |= PCI9111_INT_CTRL_ISC0;
+
+ if (irq_1_source == irq_on_external_trigger)
+ flags |= PCI9111_INT_CTRL_ISC1;
+
+ outb(flags, dev->iobase + PCI9111_INT_CTRL_REG);
+}
+
+static void pci9111_fifo_reset(struct comedi_device *dev)
+{
+ unsigned long int_ctrl_reg = dev->iobase + PCI9111_INT_CTRL_REG;
+
+ /* To reset the FIFO, set FFEN sequence as 0 -> 1 -> 0 */
+ outb(0, int_ctrl_reg);
+ outb(PCI9111_INT_CTRL_FFEN, int_ctrl_reg);
+ outb(0, int_ctrl_reg);
+}
+
+static int pci9111_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+
+ /* Disable interrupts */
+ plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true,
+ true, false);
+
+ /* disable A/D triggers (software trigger mode) and auto scan off */
+ outb(0, dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+
+ pci9111_fifo_reset(dev);
+
+ return 0;
+}
+
+static int pci9111_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (chan != i) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels,counting upwards from 0\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same reference\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int pci9111_ai_do_cmd_test(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ if (cmd->scan_begin_src != cmd->convert_src)
+ err |= -EINVAL;
+ }
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ PCI9111_AI_ACQUISITION_PERIOD_MIN_NS);
+ } else { /* TRIG_EXT */
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ PCI9111_AI_ACQUISITION_PERIOD_MIN_NS);
+ } else { /* TRIG_FOLLOW || TRIG_EXT */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ /*
+ * There's only one timer on this card, so the scan_begin timer
+ * must be a multiple of chanlist_len*convert_arg
+ */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->chanlist_len * cmd->convert_arg;
+
+ if (arg < cmd->scan_begin_arg)
+ arg *= (cmd->scan_begin_arg / arg);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= pci9111_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int pci9111_ai_do_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]);
+ unsigned int trig = 0;
+
+ /* Set channel scan limit */
+ /* PCI9111 allows only scanning from channel 0 to channel n */
+ /* TODO: handle the case of an external multiplexer */
+
+ if (cmd->chanlist_len > 1)
+ trig |= PCI9111_AI_TRIG_CTRL_ASCAN;
+
+ outb(last_chan, dev->iobase + PCI9111_AI_CHANNEL_REG);
+
+ /* Set gain */
+ /* This is the same gain on every channel */
+
+ outb(CR_RANGE(cmd->chanlist[0]) & PCI9111_AI_RANGE_MASK,
+ dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+
+ /* Set timer pacer */
+ dev_private->scan_delay = 0;
+ if (cmd->convert_src == TRIG_TIMER) {
+ trig |= PCI9111_AI_TRIG_CTRL_TPST;
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ pci9111_fifo_reset(dev);
+ pci9111_interrupt_source_set(dev, irq_on_fifo_half_full,
+ irq_on_timer_tick);
+ plx9050_interrupt_control(dev_private->lcr_io_base, true, true,
+ false, true, true);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ dev_private->scan_delay = (cmd->scan_begin_arg /
+ (cmd->convert_arg * cmd->chanlist_len)) - 1;
+ }
+ } else { /* TRIG_EXT */
+ trig |= PCI9111_AI_TRIG_CTRL_ETIS;
+ pci9111_fifo_reset(dev);
+ pci9111_interrupt_source_set(dev, irq_on_fifo_half_full,
+ irq_on_timer_tick);
+ plx9050_interrupt_control(dev_private->lcr_io_base, true, true,
+ false, true, true);
+ }
+ outb(trig, dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+
+ dev_private->chunk_counter = 0;
+ dev_private->chunk_num_samples = cmd->chanlist_len *
+ (1 + dev_private->scan_delay);
+
+ return 0;
+}
+
+static void pci9111_ai_munge(struct comedi_device *dev,
+ struct comedi_subdevice *s, void *data,
+ unsigned int num_bytes,
+ unsigned int start_chan_index)
+{
+ unsigned short *array = data;
+ unsigned int maxdata = s->maxdata;
+ unsigned int invert = (maxdata + 1) >> 1;
+ unsigned int shift = (maxdata == 0xffff) ? 0 : 4;
+ unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes);
+ unsigned int i;
+
+ for (i = 0; i < num_samples; i++)
+ array[i] = ((array[i] >> shift) & maxdata) ^ invert;
+}
+
+static void pci9111_handle_fifo_half_full(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9111_private_data *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int samples;
+
+ samples = comedi_nsamples_left(s, PCI9111_FIFO_HALF_SIZE);
+ insw(dev->iobase + PCI9111_AI_FIFO_REG,
+ devpriv->ai_bounce_buffer, samples);
+
+ if (devpriv->scan_delay < 1) {
+ comedi_buf_write_samples(s, devpriv->ai_bounce_buffer, samples);
+ } else {
+ unsigned int pos = 0;
+ unsigned int to_read;
+
+ while (pos < samples) {
+ if (devpriv->chunk_counter < cmd->chanlist_len) {
+ to_read = cmd->chanlist_len -
+ devpriv->chunk_counter;
+
+ if (to_read > samples - pos)
+ to_read = samples - pos;
+
+ comedi_buf_write_samples(s,
+ devpriv->ai_bounce_buffer + pos,
+ to_read);
+ } else {
+ to_read = devpriv->chunk_num_samples -
+ devpriv->chunk_counter;
+
+ if (to_read > samples - pos)
+ to_read = samples - pos;
+ }
+
+ pos += to_read;
+ devpriv->chunk_counter += to_read;
+
+ if (devpriv->chunk_counter >=
+ devpriv->chunk_num_samples)
+ devpriv->chunk_counter = 0;
+ }
+ }
+}
+
+static irqreturn_t pci9111_interrupt(int irq, void *p_device)
+{
+ struct comedi_device *dev = p_device;
+ struct pci9111_private_data *dev_private = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ unsigned int status;
+ unsigned long irq_flags;
+ unsigned char intcsr;
+
+ if (!dev->attached) {
+ /* Ignore interrupt before device fully attached. */
+ /* Might not even have allocated subdevices yet! */
+ return IRQ_NONE;
+ }
+
+ async = s->async;
+ cmd = &async->cmd;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+
+ /* Check if we are source of interrupt */
+ intcsr = inb(dev_private->lcr_io_base + PLX9052_INTCSR);
+ if (!(((intcsr & PLX9052_INTCSR_PCIENAB) != 0) &&
+ (((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) ||
+ ((intcsr & PCI9111_LI2_ACTIVE) == PCI9111_LI2_ACTIVE)))) {
+ /* Not the source of the interrupt. */
+ /* (N.B. not using PLX9052_INTCSR_SOFTINT) */
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ return IRQ_NONE;
+ }
+
+ if ((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) {
+ /* Interrupt comes from fifo_half-full signal */
+
+ status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+
+ /* '0' means FIFO is full, data may have been lost */
+ if (!(status & PCI9111_AI_STAT_FF_FF)) {
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ dev_dbg(dev->class_dev, "fifo overflow\n");
+ outb(0, dev->iobase + PCI9111_INT_CLR_REG);
+ async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+ }
+
+ /* '0' means FIFO is half-full */
+ if (!(status & PCI9111_AI_STAT_FF_HF))
+ pci9111_handle_fifo_half_full(dev, s);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+
+ outb(0, dev->iobase + PCI9111_INT_CLR_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int pci9111_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+ if (status & PCI9111_AI_STAT_FF_EF)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci9111_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int maxdata = s->maxdata;
+ unsigned int invert = (maxdata + 1) >> 1;
+ unsigned int shift = (maxdata == 0xffff) ? 0 : 4;
+ unsigned int status;
+ int ret;
+ int i;
+
+ outb(chan, dev->iobase + PCI9111_AI_CHANNEL_REG);
+
+ status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+ if ((status & PCI9111_AI_RANGE_MASK) != range) {
+ outb(range & PCI9111_AI_RANGE_MASK,
+ dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+ }
+
+ pci9111_fifo_reset(dev);
+
+ for (i = 0; i < insn->n; i++) {
+ /* Generate a software trigger */
+ outb(0, dev->iobase + PCI9111_SOFT_TRIG_REG);
+
+ ret = comedi_timeout(dev, s, insn, pci9111_ai_eoc, 0);
+ if (ret) {
+ pci9111_fifo_reset(dev);
+ return ret;
+ }
+
+ data[i] = inw(dev->iobase + PCI9111_AI_FIFO_REG);
+ data[i] = ((data[i] >> shift) & maxdata) ^ invert;
+ }
+
+ return i;
+}
+
+static int pci9111_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, dev->iobase + PCI9111_AO_REG);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pci9111_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inw(dev->iobase + PCI9111_DIO_REG);
+
+ return insn->n;
+}
+
+static int pci9111_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI9111_DIO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci9111_reset(struct comedi_device *dev)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+
+ /* Set trigger source to software */
+ plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true,
+ true, false);
+
+ /* disable A/D triggers (software trigger mode) and auto scan off */
+ outb(0, dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+
+ return 0;
+}
+
+static int pci9111_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pci9111_private_data *dev_private;
+ struct comedi_subdevice *s;
+ int ret;
+
+ dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private));
+ if (!dev_private)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev_private->lcr_io_base = pci_resource_start(pcidev, 1);
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ pci9111_reset(dev);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, pci9111_interrupt,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ dev->pacer = comedi_8254_init(dev->iobase + PCI9111_8254_BASE_REG,
+ I8254_OSC_BASE_2MHZ, I8254_IO16, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON;
+ s->n_chan = 16;
+ s->maxdata = 0xffff;
+ s->range_table = &pci9111_ai_range;
+ s->insn_read = pci9111_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = pci9111_ai_do_cmd_test;
+ s->do_cmd = pci9111_ai_do_cmd;
+ s->cancel = pci9111_ai_cancel;
+ s->munge = pci9111_ai_munge;
+ }
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_COMMON;
+ s->n_chan = 1;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 1;
+ s->range_table = &range_bipolar10;
+ s->insn_write = pci9111_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci9111_di_insn_bits;
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci9111_do_insn_bits;
+
+ return 0;
+}
+
+static void pci9111_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ pci9111_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver adl_pci9111_driver = {
+ .driver_name = "adl_pci9111",
+ .module = THIS_MODULE,
+ .auto_attach = pci9111_auto_attach,
+ .detach = pci9111_detach,
+};
+
+static int pci9111_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci9111_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id pci9111_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x9111) },
+ /* { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI9111_HG_DEVICE_ID) }, */
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, pci9111_pci_table);
+
+static struct pci_driver adl_pci9111_pci_driver = {
+ .name = "adl_pci9111",
+ .id_table = pci9111_pci_table,
+ .probe = pci9111_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci9111_driver, adl_pci9111_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adl_pci9118.c b/drivers/staging/comedi/drivers/adl_pci9118.c
new file mode 100644
index 000000000..fb3043dcf
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci9118.c
@@ -0,0 +1,1761 @@
+/*
+ * comedi/drivers/adl_pci9118.c
+ *
+ * hardware driver for ADLink cards:
+ * card: PCI-9118DG, PCI-9118HG, PCI-9118HR
+ * driver: pci9118dg, pci9118hg, pci9118hr
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ */
+
+/*
+ * Driver: adl_pci9118
+ * Description: Adlink PCI-9118DG, PCI-9118HG, PCI-9118HR
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ * Devices: [ADLink] PCI-9118DG (pci9118dg), PCI-9118HG (pci9118hg),
+ * PCI-9118HR (pci9118hr)
+ * Status: works
+ *
+ * This driver supports AI, AO, DI and DO subdevices.
+ * AI subdevice supports cmd and insn interface,
+ * other subdevices support only insn interface.
+ * For AI:
+ * - If cmd->scan_begin_src=TRIG_EXT then trigger input is TGIN (pin 46).
+ * - If cmd->convert_src=TRIG_EXT then trigger input is EXTTRG (pin 44).
+ * - If cmd->start_src/stop_src=TRIG_EXT then trigger input is TGIN (pin 46).
+ * - It is not necessary to have cmd.scan_end_arg=cmd.chanlist_len but
+ * cmd.scan_end_arg modulo cmd.chanlist_len must by 0.
+ * - If return value of cmdtest is 5 then you've bad channel list
+ * (it isn't possible mixture S.E. and DIFF inputs or bipolar and unipolar
+ * ranges).
+ *
+ * There are some hardware limitations:
+ * a) You cann't use mixture of unipolar/bipoar ranges or differencial/single
+ * ended inputs.
+ * b) DMA transfers must have the length aligned to two samples (32 bit),
+ * so there is some problems if cmd->chanlist_len is odd. This driver tries
+ * bypass this with adding one sample to the end of the every scan and discard
+ * it on output but this can't be used if cmd->scan_begin_src=TRIG_FOLLOW
+ * and is used flag CMDF_WAKE_EOS, then driver switch to interrupt driven mode
+ * with interrupt after every sample.
+ * c) If isn't used DMA then you can use only mode where
+ * cmd->scan_begin_src=TRIG_FOLLOW.
+ *
+ * Configuration options:
+ * [0] - PCI bus of device (optional)
+ * [1] - PCI slot of device (optional)
+ * If bus/slot is not specified, then first available PCI
+ * card will be used.
+ * [2] - 0= standard 8 DIFF/16 SE channels configuration
+ * n = external multiplexer connected, 1 <= n <= 256
+ * [3] - ignored
+ * [4] - sample&hold signal - card can generate signal for external S&H board
+ * 0 = use SSHO(pin 45) signal is generated in onboard hardware S&H logic
+ * 0 != use ADCHN7(pin 23) signal is generated from driver, number say how
+ * long delay is requested in ns and sign polarity of the hold
+ * (in this case external multiplexor can serve only 128 channels)
+ * [5] - ignored
+ */
+
+/*
+ * FIXME
+ *
+ * All the supported boards have the same PCI vendor and device IDs, so
+ * auto-attachment of PCI devices will always find the first board type.
+ *
+ * Perhaps the boards have different subdevice IDs that we could use to
+ * distinguish them?
+ *
+ * Need some device attributes so the board type can be corrected after
+ * attachment if necessary, and possibly to set other options supported by
+ * manual attachment.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include "../comedi_pci.h"
+
+#include "amcc_s5933.h"
+#include "comedi_8254.h"
+
+#define IORANGE_9118 64 /* I hope */
+#define PCI9118_CHANLEN 255 /*
+ * len of chanlist, some source say 256,
+ * but reality looks like 255 :-(
+ */
+
+/*
+ * PCI BAR2 Register map (dev->iobase)
+ */
+#define PCI9118_TIMER_BASE 0x00
+#define PCI9118_AI_FIFO_REG 0x10
+#define PCI9118_AO_REG(x) (0x10 + ((x) * 4))
+#define PCI9118_AI_STATUS_REG 0x18
+#define PCI9118_AI_STATUS_NFULL (1 << 8) /* 0=FIFO full (fatal) */
+#define PCI9118_AI_STATUS_NHFULL (1 << 7) /* 0=FIFO half full */
+#define PCI9118_AI_STATUS_NEPTY (1 << 6) /* 0=FIFO empty */
+#define PCI9118_AI_STATUS_ACMP (1 << 5) /* 1=about trigger complete */
+#define PCI9118_AI_STATUS_DTH (1 << 4) /* 1=ext. digital trigger */
+#define PCI9118_AI_STATUS_BOVER (1 << 3) /* 1=burst overrun (fatal) */
+#define PCI9118_AI_STATUS_ADOS (1 << 2) /* 1=A/D over speed (warn) */
+#define PCI9118_AI_STATUS_ADOR (1 << 1) /* 1=A/D overrun (fatal) */
+#define PCI9118_AI_STATUS_ADRDY (1 << 0) /* 1=A/D ready */
+#define PCI9118_AI_CTRL_REG 0x18
+#define PCI9118_AI_CTRL_UNIP (1 << 7) /* 1=unipolar */
+#define PCI9118_AI_CTRL_DIFF (1 << 6) /* 1=differential inputs */
+#define PCI9118_AI_CTRL_SOFTG (1 << 5) /* 1=8254 software gate */
+#define PCI9118_AI_CTRL_EXTG (1 << 4) /* 1=8254 TGIN(pin 46) gate */
+#define PCI9118_AI_CTRL_EXTM (1 << 3) /* 1=ext. trigger (pin 44) */
+#define PCI9118_AI_CTRL_TMRTR (1 << 2) /* 1=8254 is trigger source */
+#define PCI9118_AI_CTRL_INT (1 << 1) /* 1=enable interrupt */
+#define PCI9118_AI_CTRL_DMA (1 << 0) /* 1=enable DMA */
+#define PCI9118_DIO_REG 0x1c
+#define PCI9118_SOFTTRG_REG 0x20
+#define PCI9118_AI_CHANLIST_REG 0x24
+#define PCI9118_AI_CHANLIST_RANGE(x) (((x) & 0x3) << 8)
+#define PCI9118_AI_CHANLIST_CHAN(x) ((x) << 0)
+#define PCI9118_AI_BURST_NUM_REG 0x28
+#define PCI9118_AI_AUTOSCAN_MODE_REG 0x2c
+#define PCI9118_AI_CFG_REG 0x30
+#define PCI9118_AI_CFG_PDTRG (1 << 7) /* 1=positive trigger */
+#define PCI9118_AI_CFG_PETRG (1 << 6) /* 1=positive ext. trigger */
+#define PCI9118_AI_CFG_BSSH (1 << 5) /* 1=with sample & hold */
+#define PCI9118_AI_CFG_BM (1 << 4) /* 1=burst mode */
+#define PCI9118_AI_CFG_BS (1 << 3) /* 1=burst mode start */
+#define PCI9118_AI_CFG_PM (1 << 2) /* 1=post trigger */
+#define PCI9118_AI_CFG_AM (1 << 1) /* 1=about trigger */
+#define PCI9118_AI_CFG_START (1 << 0) /* 1=trigger start */
+#define PCI9118_FIFO_RESET_REG 0x34
+#define PCI9118_INT_CTRL_REG 0x38
+#define PCI9118_INT_CTRL_TIMER (1 << 3) /* timer interrupt */
+#define PCI9118_INT_CTRL_ABOUT (1 << 2) /* about trigger complete */
+#define PCI9118_INT_CTRL_HFULL (1 << 1) /* A/D FIFO half full */
+#define PCI9118_INT_CTRL_DTRG (1 << 0) /* ext. digital trigger */
+
+#define START_AI_EXT 0x01 /* start measure on external trigger */
+#define STOP_AI_EXT 0x02 /* stop measure on external trigger */
+#define STOP_AI_INT 0x08 /* stop measure on internal trigger */
+
+#define PCI9118_HALF_FIFO_SZ (1024 / 2)
+
+static const struct comedi_lrange pci9118_ai_range = {
+ 8, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange pci9118hg_ai_range = {
+ 8, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.005),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01)
+ }
+};
+
+#define PCI9118_BIPOLAR_RANGES 4 /*
+ * used for test on mixture
+ * of BIP/UNI ranges
+ */
+
+enum pci9118_boardid {
+ BOARD_PCI9118DG,
+ BOARD_PCI9118HG,
+ BOARD_PCI9118HR,
+};
+
+struct pci9118_boardinfo {
+ const char *name;
+ unsigned int ai_is_16bit:1;
+ unsigned int is_hg:1;
+};
+
+static const struct pci9118_boardinfo pci9118_boards[] = {
+ [BOARD_PCI9118DG] = {
+ .name = "pci9118dg",
+ },
+ [BOARD_PCI9118HG] = {
+ .name = "pci9118hg",
+ .is_hg = 1,
+ },
+ [BOARD_PCI9118HR] = {
+ .name = "pci9118hr",
+ .ai_is_16bit = 1,
+ },
+};
+
+struct pci9118_dmabuf {
+ unsigned short *virt; /* virtual address of buffer */
+ dma_addr_t hw; /* hardware (bus) address of buffer */
+ unsigned int size; /* size of dma buffer in bytes */
+ unsigned int use_size; /* which size we may now use for transfer */
+};
+
+struct pci9118_private {
+ unsigned long iobase_a; /* base+size for AMCC chip */
+ unsigned int master:1;
+ unsigned int dma_doublebuf:1;
+ unsigned int ai_neverending:1;
+ unsigned int usedma:1;
+ unsigned int usemux:1;
+ unsigned char ai_ctrl;
+ unsigned char int_ctrl;
+ unsigned char ai_cfg;
+ unsigned int ai_do; /* what do AI? 0=nothing, 1 to 4 mode */
+ unsigned int ai_n_realscanlen; /*
+ * what we must transfer for one
+ * outgoing scan include front/back adds
+ */
+ unsigned int ai_act_dmapos; /* position in actual real stream */
+ unsigned int ai_add_front; /*
+ * how many channels we must add
+ * before scan to satisfy S&H?
+ */
+ unsigned int ai_add_back; /*
+ * how many channels we must add
+ * before scan to satisfy DMA?
+ */
+ unsigned int ai_flags;
+ char ai12_startstop; /*
+ * measure can start/stop
+ * on external trigger
+ */
+ unsigned int dma_actbuf; /* which buffer is used now */
+ struct pci9118_dmabuf dmabuf[2];
+ int softsshdelay; /*
+ * >0 use software S&H,
+ * numer is requested delay in ns
+ */
+ unsigned char softsshsample; /*
+ * polarity of S&H signal
+ * in sample state
+ */
+ unsigned char softsshhold; /*
+ * polarity of S&H signal
+ * in hold state
+ */
+ unsigned int ai_ns_min;
+};
+
+static void pci9118_amcc_setup_dma(struct comedi_device *dev, unsigned int buf)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[buf];
+
+ /* set the master write address and transfer count */
+ outl(dmabuf->hw, devpriv->iobase_a + AMCC_OP_REG_MWAR);
+ outl(dmabuf->use_size, devpriv->iobase_a + AMCC_OP_REG_MWTC);
+}
+
+static void pci9118_amcc_dma_ena(struct comedi_device *dev, bool enable)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int mcsr;
+
+ mcsr = inl(devpriv->iobase_a + AMCC_OP_REG_MCSR);
+ if (enable)
+ mcsr |= RESET_A2P_FLAGS | A2P_HI_PRIORITY | EN_A2P_TRANSFERS;
+ else
+ mcsr &= ~EN_A2P_TRANSFERS;
+ outl(mcsr, devpriv->iobase_a + AMCC_OP_REG_MCSR);
+}
+
+static void pci9118_amcc_int_ena(struct comedi_device *dev, bool enable)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int intcsr;
+
+ /* enable/disable interrupt for AMCC Incoming Mailbox 4 (32-bit) */
+ intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+ if (enable)
+ intcsr |= 0x1f00;
+ else
+ intcsr &= ~0x1f00;
+ outl(intcsr, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+}
+
+static void pci9118_ai_reset_fifo(struct comedi_device *dev)
+{
+ /* writing any value resets the A/D FIFO */
+ outl(0, dev->iobase + PCI9118_FIFO_RESET_REG);
+}
+
+static int check_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s, int n_chan,
+ unsigned int *chanlist, int frontadd, int backadd)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int i, differencial = 0, bipolar = 0;
+
+ /* correct channel and range number check itself comedi/range.c */
+ if (n_chan < 1) {
+ dev_err(dev->class_dev, "range/channel list is empty!\n");
+ return 0;
+ }
+ if ((frontadd + n_chan + backadd) > s->len_chanlist) {
+ dev_err(dev->class_dev,
+ "range/channel list is too long for actual configuration!\n");
+ return 0;
+ }
+
+ if (CR_AREF(chanlist[0]) == AREF_DIFF)
+ differencial = 1; /* all input must be diff */
+ if (CR_RANGE(chanlist[0]) < PCI9118_BIPOLAR_RANGES)
+ bipolar = 1; /* all input must be bipolar */
+ if (n_chan > 1)
+ for (i = 1; i < n_chan; i++) { /* check S.E/diff */
+ if ((CR_AREF(chanlist[i]) == AREF_DIFF) !=
+ (differencial)) {
+ dev_err(dev->class_dev,
+ "Differential and single ended inputs can't be mixed!\n");
+ return 0;
+ }
+ if ((CR_RANGE(chanlist[i]) < PCI9118_BIPOLAR_RANGES) !=
+ (bipolar)) {
+ dev_err(dev->class_dev,
+ "Bipolar and unipolar ranges can't be mixed!\n");
+ return 0;
+ }
+ if (!devpriv->usemux && differencial &&
+ (CR_CHAN(chanlist[i]) >= (s->n_chan / 2))) {
+ dev_err(dev->class_dev,
+ "AREF_DIFF is only available for the first 8 channels!\n");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static void pci9118_set_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ int n_chan, unsigned int *chanlist,
+ int frontadd, int backadd)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int chan0 = CR_CHAN(chanlist[0]);
+ unsigned int range0 = CR_RANGE(chanlist[0]);
+ unsigned int aref0 = CR_AREF(chanlist[0]);
+ unsigned int ssh = 0x00;
+ unsigned int val;
+ int i;
+
+ /*
+ * Configure analog input based on the first chanlist entry.
+ * All entries are either unipolar or bipolar and single-ended
+ * or differential.
+ */
+ devpriv->ai_ctrl = 0;
+ if (comedi_range_is_unipolar(s, range0))
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_UNIP;
+ if (aref0 == AREF_DIFF)
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_DIFF;
+ outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
+
+ /* gods know why this sequence! */
+ outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+ outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+ outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+
+ /* insert channels for S&H */
+ if (frontadd) {
+ val = PCI9118_AI_CHANLIST_CHAN(chan0) |
+ PCI9118_AI_CHANLIST_RANGE(range0);
+ ssh = devpriv->softsshsample;
+ for (i = 0; i < frontadd; i++) {
+ outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
+ ssh = devpriv->softsshhold;
+ }
+ }
+
+ /* store chanlist */
+ for (i = 0; i < n_chan; i++) {
+ unsigned int chan = CR_CHAN(chanlist[i]);
+ unsigned int range = CR_RANGE(chanlist[i]);
+
+ val = PCI9118_AI_CHANLIST_CHAN(chan) |
+ PCI9118_AI_CHANLIST_RANGE(range);
+ outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
+ }
+
+ /* insert channels to fit onto 32bit DMA */
+ if (backadd) {
+ val = PCI9118_AI_CHANLIST_CHAN(chan0) |
+ PCI9118_AI_CHANLIST_RANGE(range0);
+ for (i = 0; i < backadd; i++)
+ outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
+ }
+ /* close scan queue */
+ outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+ /* udelay(100); important delay, or first sample will be crippled */
+}
+
+static void interrupt_pci9118_ai_mode4_switch(struct comedi_device *dev,
+ unsigned int next_buf)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[next_buf];
+
+ devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG |
+ PCI9118_AI_CFG_AM;
+ outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
+ comedi_8254_load(dev->pacer, 0, dmabuf->hw >> 1,
+ I8254_MODE0 | I8254_BINARY);
+ devpriv->ai_cfg |= PCI9118_AI_CFG_START;
+ outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
+}
+
+static unsigned int valid_samples_in_act_dma_buf(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int n_raw_samples)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int start_pos = devpriv->ai_add_front;
+ unsigned int stop_pos = start_pos + cmd->chanlist_len;
+ unsigned int span_len = stop_pos + devpriv->ai_add_back;
+ unsigned int dma_pos = devpriv->ai_act_dmapos;
+ unsigned int whole_spans, n_samples, x;
+
+ if (span_len == cmd->chanlist_len)
+ return n_raw_samples; /* use all samples */
+
+ /*
+ * Not all samples are to be used. Buffer contents consist of a
+ * possibly non-whole number of spans and a region of each span
+ * is to be used.
+ *
+ * Account for samples in whole number of spans.
+ */
+ whole_spans = n_raw_samples / span_len;
+ n_samples = whole_spans * cmd->chanlist_len;
+ n_raw_samples -= whole_spans * span_len;
+
+ /*
+ * Deal with remaining samples which could overlap up to two spans.
+ */
+ while (n_raw_samples) {
+ if (dma_pos < start_pos) {
+ /* Skip samples before start position. */
+ x = start_pos - dma_pos;
+ if (x > n_raw_samples)
+ x = n_raw_samples;
+ dma_pos += x;
+ n_raw_samples -= x;
+ if (!n_raw_samples)
+ break;
+ }
+ if (dma_pos < stop_pos) {
+ /* Include samples before stop position. */
+ x = stop_pos - dma_pos;
+ if (x > n_raw_samples)
+ x = n_raw_samples;
+ n_samples += x;
+ dma_pos += x;
+ n_raw_samples -= x;
+ }
+ /* Advance to next span. */
+ start_pos += span_len;
+ stop_pos += span_len;
+ }
+ return n_samples;
+}
+
+static void move_block_from_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *dma_buffer,
+ unsigned int n_raw_samples)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int start_pos = devpriv->ai_add_front;
+ unsigned int stop_pos = start_pos + cmd->chanlist_len;
+ unsigned int span_len = stop_pos + devpriv->ai_add_back;
+ unsigned int dma_pos = devpriv->ai_act_dmapos;
+ unsigned int x;
+
+ if (span_len == cmd->chanlist_len) {
+ /* All samples are to be copied. */
+ comedi_buf_write_samples(s, dma_buffer, n_raw_samples);
+ dma_pos += n_raw_samples;
+ } else {
+ /*
+ * Not all samples are to be copied. Buffer contents consist
+ * of a possibly non-whole number of spans and a region of
+ * each span is to be copied.
+ */
+ while (n_raw_samples) {
+ if (dma_pos < start_pos) {
+ /* Skip samples before start position. */
+ x = start_pos - dma_pos;
+ if (x > n_raw_samples)
+ x = n_raw_samples;
+ dma_pos += x;
+ n_raw_samples -= x;
+ if (!n_raw_samples)
+ break;
+ }
+ if (dma_pos < stop_pos) {
+ /* Copy samples before stop position. */
+ x = stop_pos - dma_pos;
+ if (x > n_raw_samples)
+ x = n_raw_samples;
+ comedi_buf_write_samples(s, dma_buffer, x);
+ dma_pos += x;
+ n_raw_samples -= x;
+ }
+ /* Advance to next span. */
+ start_pos += span_len;
+ stop_pos += span_len;
+ }
+ }
+ /* Update position in span for next time. */
+ devpriv->ai_act_dmapos = dma_pos % span_len;
+}
+
+static void pci9118_exttrg_enable(struct comedi_device *dev, bool enable)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ if (enable)
+ devpriv->int_ctrl |= PCI9118_INT_CTRL_DTRG;
+ else
+ devpriv->int_ctrl &= ~PCI9118_INT_CTRL_DTRG;
+ outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
+
+ if (devpriv->int_ctrl)
+ pci9118_amcc_int_ena(dev, true);
+ else
+ pci9118_amcc_int_ena(dev, false);
+}
+
+static void pci9118_calc_divisors(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *tim1, unsigned int *tim2,
+ unsigned int flags, int chans,
+ unsigned int *div1, unsigned int *div2,
+ unsigned int chnsshfront)
+{
+ struct comedi_8254 *pacer = dev->pacer;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ *div1 = *tim2 / pacer->osc_base; /* convert timer (burst) */
+ *div2 = *tim1 / pacer->osc_base; /* scan timer */
+ *div2 = *div2 / *div1; /* major timer is c1*c2 */
+ if (*div2 < chans)
+ *div2 = chans;
+
+ *tim2 = *div1 * pacer->osc_base; /* real convert timer */
+
+ if (cmd->convert_src == TRIG_NOW && !chnsshfront) {
+ /* use BSSH signal */
+ if (*div2 < (chans + 2))
+ *div2 = chans + 2;
+ }
+
+ *tim1 = *div1 * *div2 * pacer->osc_base;
+}
+
+static void pci9118_start_pacer(struct comedi_device *dev, int mode)
+{
+ if (mode == 1 || mode == 2 || mode == 4)
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+}
+
+static int pci9118_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ if (devpriv->usedma)
+ pci9118_amcc_dma_ena(dev, false);
+ pci9118_exttrg_enable(dev, false);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
+ /* set default config (disable burst and triggers) */
+ devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
+ outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
+ /* reset acqusition control */
+ devpriv->ai_ctrl = 0;
+ outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
+ outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
+ /* reset scan queue */
+ outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+ outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+ pci9118_ai_reset_fifo(dev);
+
+ devpriv->int_ctrl = 0;
+ outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
+ pci9118_amcc_int_ena(dev, false);
+
+ devpriv->ai_do = 0;
+ devpriv->usedma = 0;
+
+ devpriv->ai_act_dmapos = 0;
+ s->async->inttrig = NULL;
+ devpriv->ai_neverending = 0;
+ devpriv->dma_actbuf = 0;
+
+ return 0;
+}
+
+static void pci9118_ai_munge(struct comedi_device *dev,
+ struct comedi_subdevice *s, void *data,
+ unsigned int num_bytes,
+ unsigned int start_chan_index)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned short *array = data;
+ unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes);
+ unsigned int i;
+
+ for (i = 0; i < num_samples; i++) {
+ if (devpriv->usedma)
+ array[i] = be16_to_cpu(array[i]);
+ if (s->maxdata == 0xffff)
+ array[i] ^= 0x8000;
+ else
+ array[i] = (array[i] >> 4) & 0x0fff;
+ }
+}
+
+static void interrupt_pci9118_ai_onesample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned short sampl;
+
+ sampl = inl(dev->iobase + PCI9118_AI_FIFO_REG);
+
+ comedi_buf_write_samples(s, &sampl, 1);
+
+ if (!devpriv->ai_neverending) {
+ if (s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+ }
+}
+
+static void interrupt_pci9118_ai_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[devpriv->dma_actbuf];
+ unsigned int n_all = comedi_bytes_to_samples(s, dmabuf->use_size);
+ unsigned int n_valid;
+ bool more_dma;
+
+ /* determine whether more DMA buffers to do after this one */
+ n_valid = valid_samples_in_act_dma_buf(dev, s, n_all);
+ more_dma = n_valid < comedi_nsamples_left(s, n_valid + 1);
+
+ /* switch DMA buffers and restart DMA if double buffering */
+ if (more_dma && devpriv->dma_doublebuf) {
+ devpriv->dma_actbuf = 1 - devpriv->dma_actbuf;
+ pci9118_amcc_setup_dma(dev, devpriv->dma_actbuf);
+ if (devpriv->ai_do == 4) {
+ interrupt_pci9118_ai_mode4_switch(dev,
+ devpriv->dma_actbuf);
+ }
+ }
+
+ if (n_all)
+ move_block_from_dma(dev, s, dmabuf->virt, n_all);
+
+ if (!devpriv->ai_neverending) {
+ if (s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+ }
+
+ if (s->async->events & COMEDI_CB_CANCEL_MASK)
+ more_dma = false;
+
+ /* restart DMA if not double buffering */
+ if (more_dma && !devpriv->dma_doublebuf) {
+ pci9118_amcc_setup_dma(dev, 0);
+ if (devpriv->ai_do == 4)
+ interrupt_pci9118_ai_mode4_switch(dev, 0);
+ }
+}
+
+static irqreturn_t pci9118_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int intsrc; /* IRQ reasons from card */
+ unsigned int intcsr; /* INT register from AMCC chip */
+ unsigned int adstat; /* STATUS register */
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ intsrc = inl(dev->iobase + PCI9118_INT_CTRL_REG) & 0xf;
+ intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+
+ if (!intsrc && !(intcsr & ANY_S593X_INT))
+ return IRQ_NONE;
+
+ outl(intcsr | 0x00ff0000, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+
+ if (intcsr & MASTER_ABORT_INT) {
+ dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ goto interrupt_exit;
+ }
+
+ if (intcsr & TARGET_ABORT_INT) {
+ dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ goto interrupt_exit;
+ }
+
+ adstat = inl(dev->iobase + PCI9118_AI_STATUS_REG);
+ if ((adstat & PCI9118_AI_STATUS_NFULL) == 0) {
+ dev_err(dev->class_dev,
+ "A/D FIFO Full status (Fatal Error!)\n");
+ s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
+ goto interrupt_exit;
+ }
+ if (adstat & PCI9118_AI_STATUS_BOVER) {
+ dev_err(dev->class_dev,
+ "A/D Burst Mode Overrun Status (Fatal Error!)\n");
+ s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
+ goto interrupt_exit;
+ }
+ if (adstat & PCI9118_AI_STATUS_ADOS) {
+ dev_err(dev->class_dev, "A/D Over Speed Status (Warning!)\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ goto interrupt_exit;
+ }
+ if (adstat & PCI9118_AI_STATUS_ADOR) {
+ dev_err(dev->class_dev, "A/D Overrun Status (Fatal Error!)\n");
+ s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
+ goto interrupt_exit;
+ }
+
+ if (!devpriv->ai_do)
+ return IRQ_HANDLED;
+
+ if (devpriv->ai12_startstop) {
+ if ((adstat & PCI9118_AI_STATUS_DTH) &&
+ (intsrc & PCI9118_INT_CTRL_DTRG)) {
+ /* start/stop of measure */
+ if (devpriv->ai12_startstop & START_AI_EXT) {
+ /* deactivate EXT trigger */
+ devpriv->ai12_startstop &= ~START_AI_EXT;
+ if (!(devpriv->ai12_startstop & STOP_AI_EXT))
+ pci9118_exttrg_enable(dev, false);
+
+ /* start pacer */
+ pci9118_start_pacer(dev, devpriv->ai_do);
+ outl(devpriv->ai_ctrl,
+ dev->iobase + PCI9118_AI_CTRL_REG);
+ } else if (devpriv->ai12_startstop & STOP_AI_EXT) {
+ /* deactivate EXT trigger */
+ devpriv->ai12_startstop &= ~STOP_AI_EXT;
+ pci9118_exttrg_enable(dev, false);
+
+ /* on next interrupt measure will stop */
+ devpriv->ai_neverending = 0;
+ }
+ }
+ }
+
+ if (devpriv->usedma)
+ interrupt_pci9118_ai_dma(dev, s);
+ else
+ interrupt_pci9118_ai_onesample(dev, s);
+
+interrupt_exit:
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static void pci9118_ai_cmd_start(struct comedi_device *dev)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
+ outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
+ if (devpriv->ai_do != 3) {
+ pci9118_start_pacer(dev, devpriv->ai_do);
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_SOFTG;
+ }
+ outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
+}
+
+static int pci9118_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+ pci9118_ai_cmd_start(dev);
+
+ return 1;
+}
+
+static int Compute_and_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct pci9118_dmabuf *dmabuf0 = &devpriv->dmabuf[0];
+ struct pci9118_dmabuf *dmabuf1 = &devpriv->dmabuf[1];
+ unsigned int dmalen0, dmalen1, i;
+
+ dmalen0 = dmabuf0->size;
+ dmalen1 = dmabuf1->size;
+ /* isn't output buff smaller that our DMA buff? */
+ if (dmalen0 > s->async->prealloc_bufsz) {
+ /* align to 32bit down */
+ dmalen0 = s->async->prealloc_bufsz & ~3L;
+ }
+ if (dmalen1 > s->async->prealloc_bufsz) {
+ /* align to 32bit down */
+ dmalen1 = s->async->prealloc_bufsz & ~3L;
+ }
+
+ /* we want wake up every scan? */
+ if (devpriv->ai_flags & CMDF_WAKE_EOS) {
+ if (dmalen0 < (devpriv->ai_n_realscanlen << 1)) {
+ /* uff, too short DMA buffer, disable EOS support! */
+ devpriv->ai_flags &= (~CMDF_WAKE_EOS);
+ dev_info(dev->class_dev,
+ "WAR: DMA0 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
+ dmalen0, devpriv->ai_n_realscanlen << 1);
+ } else {
+ /* short first DMA buffer to one scan */
+ dmalen0 = devpriv->ai_n_realscanlen << 1;
+ if (dmalen0 < 4) {
+ dev_info(dev->class_dev,
+ "ERR: DMA0 buf len bug? (%d<4)\n",
+ dmalen0);
+ dmalen0 = 4;
+ }
+ }
+ }
+ if (devpriv->ai_flags & CMDF_WAKE_EOS) {
+ if (dmalen1 < (devpriv->ai_n_realscanlen << 1)) {
+ /* uff, too short DMA buffer, disable EOS support! */
+ devpriv->ai_flags &= (~CMDF_WAKE_EOS);
+ dev_info(dev->class_dev,
+ "WAR: DMA1 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
+ dmalen1, devpriv->ai_n_realscanlen << 1);
+ } else {
+ /* short second DMA buffer to one scan */
+ dmalen1 = devpriv->ai_n_realscanlen << 1;
+ if (dmalen1 < 4) {
+ dev_info(dev->class_dev,
+ "ERR: DMA1 buf len bug? (%d<4)\n",
+ dmalen1);
+ dmalen1 = 4;
+ }
+ }
+ }
+
+ /* transfer without CMDF_WAKE_EOS */
+ if (!(devpriv->ai_flags & CMDF_WAKE_EOS)) {
+ /* if it's possible then align DMA buffers to length of scan */
+ i = dmalen0;
+ dmalen0 =
+ (dmalen0 / (devpriv->ai_n_realscanlen << 1)) *
+ (devpriv->ai_n_realscanlen << 1);
+ dmalen0 &= ~3L;
+ if (!dmalen0)
+ dmalen0 = i; /* uff. very long scan? */
+ i = dmalen1;
+ dmalen1 =
+ (dmalen1 / (devpriv->ai_n_realscanlen << 1)) *
+ (devpriv->ai_n_realscanlen << 1);
+ dmalen1 &= ~3L;
+ if (!dmalen1)
+ dmalen1 = i; /* uff. very long scan? */
+ /*
+ * if measure isn't neverending then test, if it fits whole
+ * into one or two DMA buffers
+ */
+ if (!devpriv->ai_neverending) {
+ /* fits whole measure into one DMA buffer? */
+ if (dmalen0 >
+ ((devpriv->ai_n_realscanlen << 1) *
+ cmd->stop_arg)) {
+ dmalen0 =
+ (devpriv->ai_n_realscanlen << 1) *
+ cmd->stop_arg;
+ dmalen0 &= ~3L;
+ } else { /*
+ * fits whole measure into
+ * two DMA buffer?
+ */
+ if (dmalen1 >
+ ((devpriv->ai_n_realscanlen << 1) *
+ cmd->stop_arg - dmalen0))
+ dmalen1 =
+ (devpriv->ai_n_realscanlen << 1) *
+ cmd->stop_arg - dmalen0;
+ dmalen1 &= ~3L;
+ }
+ }
+ }
+
+ /* these DMA buffer size will be used */
+ devpriv->dma_actbuf = 0;
+ dmabuf0->use_size = dmalen0;
+ dmabuf1->use_size = dmalen1;
+
+ pci9118_amcc_dma_ena(dev, false);
+ pci9118_amcc_setup_dma(dev, 0);
+ /* init DMA transfer */
+ outl(0x00000000 | AINT_WRITE_COMPL,
+ devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+/* outl(0x02000000|AINT_WRITE_COMPL, devpriv->iobase_a+AMCC_OP_REG_INTCSR); */
+ pci9118_amcc_dma_ena(dev, true);
+ outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | EN_A2P_TRANSFERS,
+ devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+ /* allow bus mastering */
+
+ return 0;
+}
+
+static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_8254 *pacer = dev->pacer;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int addchans = 0;
+
+ devpriv->ai12_startstop = 0;
+ devpriv->ai_flags = cmd->flags;
+ devpriv->ai_add_front = 0;
+ devpriv->ai_add_back = 0;
+
+ /* prepare for start/stop conditions */
+ if (cmd->start_src == TRIG_EXT)
+ devpriv->ai12_startstop |= START_AI_EXT;
+ if (cmd->stop_src == TRIG_EXT) {
+ devpriv->ai_neverending = 1;
+ devpriv->ai12_startstop |= STOP_AI_EXT;
+ }
+ if (cmd->stop_src == TRIG_NONE)
+ devpriv->ai_neverending = 1;
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->ai_neverending = 0;
+
+ /*
+ * use additional sample at end of every scan
+ * to satisty DMA 32 bit transfer?
+ */
+ devpriv->ai_add_front = 0;
+ devpriv->ai_add_back = 0;
+ if (devpriv->master) {
+ devpriv->usedma = 1;
+ if ((cmd->flags & CMDF_WAKE_EOS) &&
+ (cmd->scan_end_arg == 1)) {
+ if (cmd->convert_src == TRIG_NOW)
+ devpriv->ai_add_back = 1;
+ if (cmd->convert_src == TRIG_TIMER) {
+ devpriv->usedma = 0;
+ /*
+ * use INT transfer if scanlist
+ * have only one channel
+ */
+ }
+ }
+ if ((cmd->flags & CMDF_WAKE_EOS) &&
+ (cmd->scan_end_arg & 1) &&
+ (cmd->scan_end_arg > 1)) {
+ if (cmd->scan_begin_src == TRIG_FOLLOW) {
+ devpriv->usedma = 0;
+ /*
+ * XXX maybe can be corrected to use 16 bit DMA
+ */
+ } else { /*
+ * well, we must insert one sample
+ * to end of EOS to meet 32 bit transfer
+ */
+ devpriv->ai_add_back = 1;
+ }
+ }
+ } else { /* interrupt transfer don't need any correction */
+ devpriv->usedma = 0;
+ }
+
+ /*
+ * we need software S&H signal?
+ * It adds two samples before every scan as minimum
+ */
+ if (cmd->convert_src == TRIG_NOW && devpriv->softsshdelay) {
+ devpriv->ai_add_front = 2;
+ if ((devpriv->usedma == 1) && (devpriv->ai_add_back == 1)) {
+ /* move it to front */
+ devpriv->ai_add_front++;
+ devpriv->ai_add_back = 0;
+ }
+ if (cmd->convert_arg < devpriv->ai_ns_min)
+ cmd->convert_arg = devpriv->ai_ns_min;
+ addchans = devpriv->softsshdelay / cmd->convert_arg;
+ if (devpriv->softsshdelay % cmd->convert_arg)
+ addchans++;
+ if (addchans > (devpriv->ai_add_front - 1)) {
+ /* uff, still short */
+ devpriv->ai_add_front = addchans + 1;
+ if (devpriv->usedma == 1)
+ if ((devpriv->ai_add_front +
+ cmd->chanlist_len +
+ devpriv->ai_add_back) & 1)
+ devpriv->ai_add_front++;
+ /* round up to 32 bit */
+ }
+ }
+ /* well, we now know what must be all added */
+ devpriv->ai_n_realscanlen = /*
+ * what we must take from card in real
+ * to have cmd->scan_end_arg on output?
+ */
+ (devpriv->ai_add_front + cmd->chanlist_len +
+ devpriv->ai_add_back) * (cmd->scan_end_arg /
+ cmd->chanlist_len);
+
+ /* check and setup channel list */
+ if (!check_channel_list(dev, s, cmd->chanlist_len,
+ cmd->chanlist, devpriv->ai_add_front,
+ devpriv->ai_add_back))
+ return -EINVAL;
+
+ /*
+ * Configure analog input and load the chanlist.
+ * The acqusition control bits are enabled later.
+ */
+ pci9118_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist,
+ devpriv->ai_add_front, devpriv->ai_add_back);
+
+ /* Determine acqusition mode and calculate timing */
+ devpriv->ai_do = 0;
+ if (cmd->scan_begin_src != TRIG_TIMER &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* cascaded timers 1 and 2 are used for convert timing */
+ if (cmd->scan_begin_src == TRIG_EXT)
+ devpriv->ai_do = 4;
+ else
+ devpriv->ai_do = 1;
+
+ comedi_8254_cascade_ns_to_timer(pacer, &cmd->convert_arg,
+ devpriv->ai_flags &
+ CMDF_ROUND_NEAREST);
+ comedi_8254_update_divisors(pacer);
+
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;
+
+ if (!devpriv->usedma) {
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_INT;
+ devpriv->int_ctrl |= PCI9118_INT_CTRL_TIMER;
+ }
+
+ if (cmd->scan_begin_src == TRIG_EXT) {
+ struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[0];
+
+ devpriv->ai_cfg |= PCI9118_AI_CFG_AM;
+ outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
+ comedi_8254_load(pacer, 0, dmabuf->hw >> 1,
+ I8254_MODE0 | I8254_BINARY);
+ devpriv->ai_cfg |= PCI9118_AI_CFG_START;
+ }
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src != TRIG_EXT) {
+ if (!devpriv->usedma) {
+ dev_err(dev->class_dev,
+ "cmd->scan_begin_src=TRIG_TIMER works only with bus mastering!\n");
+ return -EIO;
+ }
+
+ /* double timed action */
+ devpriv->ai_do = 2;
+
+ pci9118_calc_divisors(dev, s,
+ &cmd->scan_begin_arg, &cmd->convert_arg,
+ devpriv->ai_flags,
+ devpriv->ai_n_realscanlen,
+ &pacer->divisor1,
+ &pacer->divisor2,
+ devpriv->ai_add_front);
+
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;
+ devpriv->ai_cfg |= PCI9118_AI_CFG_BM | PCI9118_AI_CFG_BS;
+ if (cmd->convert_src == TRIG_NOW && !devpriv->softsshdelay)
+ devpriv->ai_cfg |= PCI9118_AI_CFG_BSSH;
+ outl(devpriv->ai_n_realscanlen,
+ dev->iobase + PCI9118_AI_BURST_NUM_REG);
+ }
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_EXT) {
+ /* external trigger conversion */
+ devpriv->ai_do = 3;
+
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_EXTM;
+ }
+
+ if (devpriv->ai_do == 0) {
+ dev_err(dev->class_dev,
+ "Unable to determine acqusition mode! BUG in (*do_cmdtest)?\n");
+ return -EINVAL;
+ }
+
+ if (devpriv->usedma)
+ devpriv->ai_ctrl |= PCI9118_AI_CTRL_DMA;
+
+ /* set default config (disable burst and triggers) */
+ devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
+ outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
+ udelay(1);
+ pci9118_ai_reset_fifo(dev);
+
+ /* clear A/D and INT status registers */
+ inl(dev->iobase + PCI9118_AI_STATUS_REG);
+ inl(dev->iobase + PCI9118_INT_CTRL_REG);
+
+ devpriv->ai_act_dmapos = 0;
+
+ if (devpriv->usedma) {
+ Compute_and_setup_dma(dev, s);
+
+ outl(0x02000000 | AINT_WRITE_COMPL,
+ devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+ } else {
+ pci9118_amcc_int_ena(dev, true);
+ }
+
+ /* start async command now or wait for internal trigger */
+ if (cmd->start_src == TRIG_NOW)
+ pci9118_ai_cmd_start(dev);
+ else if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = pci9118_ai_inttrig;
+
+ /* enable external trigger for command start/stop */
+ if (cmd->start_src == TRIG_EXT || cmd->stop_src == TRIG_EXT)
+ pci9118_exttrg_enable(dev, true);
+
+ return 0;
+}
+
+static int pci9118_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct pci9118_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int flags;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_EXT | TRIG_INT);
+
+ flags = TRIG_FOLLOW;
+ if (devpriv->master)
+ flags |= TRIG_TIMER | TRIG_EXT;
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags);
+
+ flags = TRIG_TIMER | TRIG_EXT;
+ if (devpriv->master)
+ flags |= TRIG_NOW;
+ err |= comedi_check_trigger_src(&cmd->convert_src, flags);
+
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_NONE | TRIG_EXT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->start_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
+ err |= -EINVAL;
+
+ if (cmd->start_src == TRIG_INT && cmd->scan_begin_src == TRIG_INT)
+ err |= -EINVAL;
+
+ if ((cmd->scan_begin_src & (TRIG_TIMER | TRIG_EXT)) &&
+ (!(cmd->convert_src & (TRIG_TIMER | TRIG_NOW))))
+ err |= -EINVAL;
+
+ if ((cmd->scan_begin_src == TRIG_FOLLOW) &&
+ (!(cmd->convert_src & (TRIG_TIMER | TRIG_EXT))))
+ err |= -EINVAL;
+
+ if (cmd->stop_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_EXT:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_INT:
+ /* start_arg is the internal trigger (any value) */
+ break;
+ }
+
+ if (cmd->scan_begin_src & (TRIG_FOLLOW | TRIG_EXT))
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if ((cmd->scan_begin_src == TRIG_TIMER) &&
+ (cmd->convert_src == TRIG_TIMER) && (cmd->scan_end_arg == 1)) {
+ cmd->scan_begin_src = TRIG_FOLLOW;
+ cmd->convert_arg = cmd->scan_begin_arg;
+ cmd->scan_begin_arg = 0;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ devpriv->ai_ns_min);
+ }
+
+ if (cmd->scan_begin_src == TRIG_EXT) {
+ if (cmd->scan_begin_arg) {
+ cmd->scan_begin_arg = 0;
+ err |= -EINVAL;
+ err |= comedi_check_trigger_arg_max(&cmd->scan_end_arg,
+ 65535);
+ }
+ }
+
+ if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ devpriv->ai_ns_min);
+ }
+
+ if (cmd->convert_src == TRIG_EXT)
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+
+ err |= comedi_check_trigger_arg_min(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if ((cmd->scan_end_arg % cmd->chanlist_len)) {
+ cmd->scan_end_arg =
+ cmd->chanlist_len * (cmd->scan_end_arg / cmd->chanlist_len);
+ err |= -EINVAL;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
+ arg = cmd->convert_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src == TRIG_NOW) {
+ if (cmd->convert_arg == 0) {
+ arg = devpriv->ai_ns_min *
+ (cmd->scan_end_arg + 2);
+ } else {
+ arg = cmd->convert_arg * cmd->chanlist_len;
+ }
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ if (cmd->chanlist)
+ if (!check_channel_list(dev, s, cmd->chanlist_len,
+ cmd->chanlist, 0, 0))
+ return 5; /* incorrect channels list */
+
+ return 0;
+}
+
+static int pci9118_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inl(dev->iobase + PCI9118_AI_STATUS_REG);
+ if (status & PCI9118_AI_STATUS_ADRDY)
+ return 0;
+ return -EBUSY;
+}
+
+static void pci9118_ai_start_conv(struct comedi_device *dev)
+{
+ /* writing any value triggers an A/D conversion */
+ outl(0, dev->iobase + PCI9118_SOFTTRG_REG);
+}
+
+static int pci9118_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int val;
+ int ret;
+ int i;
+
+ /*
+ * Configure analog input based on the chanspec.
+ * Acqusition is software controlled without interrupts.
+ */
+ pci9118_set_chanlist(dev, s, 1, &insn->chanspec, 0, 0);
+
+ /* set default config (disable burst and triggers) */
+ devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
+ outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
+
+ pci9118_ai_reset_fifo(dev);
+
+ for (i = 0; i < insn->n; i++) {
+ pci9118_ai_start_conv(dev);
+
+ ret = comedi_timeout(dev, s, insn, pci9118_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inl(dev->iobase + PCI9118_AI_FIFO_REG);
+ if (s->maxdata == 0xffff)
+ data[i] = (val & 0xffff) ^ 0x8000;
+ else
+ data[i] = (val >> 4) & 0xfff;
+ }
+
+ return insn->n;
+}
+
+static int pci9118_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outl(val, dev->iobase + PCI9118_AO_REG(chan));
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pci9118_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ /*
+ * The digital inputs and outputs share the read register.
+ * bits [7:4] are the digital outputs
+ * bits [3:0] are the digital inputs
+ */
+ data[1] = inl(dev->iobase + PCI9118_DIO_REG) & 0xf;
+
+ return insn->n;
+}
+
+static int pci9118_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ /*
+ * The digital outputs are set with the same register that
+ * the digital inputs and outputs are read from. But the
+ * outputs are set with bits [3:0] so we can simply write
+ * the s->state to set them.
+ */
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + PCI9118_DIO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pci9118_reset(struct comedi_device *dev)
+{
+ /* reset analog input subsystem */
+ outl(0, dev->iobase + PCI9118_INT_CTRL_REG);
+ outl(0, dev->iobase + PCI9118_AI_CTRL_REG);
+ outl(0, dev->iobase + PCI9118_AI_CFG_REG);
+ pci9118_ai_reset_fifo(dev);
+
+ /* clear any pending interrupts and status */
+ inl(dev->iobase + PCI9118_INT_CTRL_REG);
+ inl(dev->iobase + PCI9118_AI_STATUS_REG);
+
+ /* reset DMA and scan queue */
+ outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
+ outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+ outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
+
+ /* reset analog outputs to 0V */
+ outl(2047, dev->iobase + PCI9118_AO_REG(0));
+ outl(2047, dev->iobase + PCI9118_AO_REG(1));
+}
+
+static struct pci_dev *pci9118_find_pci(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct pci_dev *pcidev = NULL;
+ int bus = it->options[0];
+ int slot = it->options[1];
+
+ for_each_pci_dev(pcidev) {
+ if (pcidev->vendor != PCI_VENDOR_ID_AMCC)
+ continue;
+ if (pcidev->device != 0x80d9)
+ continue;
+ if (bus || slot) {
+ /* requested particular bus/slot */
+ if (pcidev->bus->number != bus ||
+ PCI_SLOT(pcidev->devfn) != slot)
+ continue;
+ }
+ return pcidev;
+ }
+ dev_err(dev->class_dev,
+ "no supported board found! (req. bus/slot : %d/%d)\n",
+ bus, slot);
+ return NULL;
+}
+
+static void pci9118_alloc_dma(struct comedi_device *dev)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct pci9118_dmabuf *dmabuf;
+ int order;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ dmabuf = &devpriv->dmabuf[i];
+ for (order = 2; order >= 0; order--) {
+ dmabuf->virt =
+ dma_alloc_coherent(dev->hw_dev, PAGE_SIZE << order,
+ &dmabuf->hw, GFP_KERNEL);
+ if (dmabuf->virt)
+ break;
+ }
+ if (!dmabuf->virt)
+ break;
+ dmabuf->size = PAGE_SIZE << order;
+
+ if (i == 0)
+ devpriv->master = 1;
+ if (i == 1)
+ devpriv->dma_doublebuf = 1;
+ }
+}
+
+static void pci9118_free_dma(struct comedi_device *dev)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct pci9118_dmabuf *dmabuf;
+ int i;
+
+ if (!devpriv)
+ return;
+
+ for (i = 0; i < 2; i++) {
+ dmabuf = &devpriv->dmabuf[i];
+ if (dmabuf->virt) {
+ dma_free_coherent(dev->hw_dev, dmabuf->size,
+ dmabuf->virt, dmabuf->hw);
+ }
+ }
+}
+
+static int pci9118_common_attach(struct comedi_device *dev,
+ int ext_mux, int softsshdelay)
+{
+ const struct pci9118_boardinfo *board = dev->board_ptr;
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pci9118_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+ u16 u16w;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ pci_set_master(pcidev);
+
+ devpriv->iobase_a = pci_resource_start(pcidev, 0);
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ dev->pacer = comedi_8254_init(dev->iobase + PCI9118_TIMER_BASE,
+ I8254_OSC_BASE_4MHZ, I8254_IO32, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ pci9118_reset(dev);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, pci9118_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = pcidev->irq;
+
+ pci9118_alloc_dma(dev);
+ }
+ }
+
+ if (ext_mux > 0) {
+ if (ext_mux > 256)
+ ext_mux = 256; /* max 256 channels! */
+ if (softsshdelay > 0)
+ if (ext_mux > 128)
+ ext_mux = 128;
+ devpriv->usemux = 1;
+ } else {
+ devpriv->usemux = 0;
+ }
+
+ if (softsshdelay < 0) {
+ /* select sample&hold signal polarity */
+ devpriv->softsshdelay = -softsshdelay;
+ devpriv->softsshsample = 0x80;
+ devpriv->softsshhold = 0x00;
+ } else {
+ devpriv->softsshdelay = softsshdelay;
+ devpriv->softsshsample = 0x00;
+ devpriv->softsshhold = 0x80;
+ }
+
+ pci_read_config_word(pcidev, PCI_COMMAND, &u16w);
+ pci_write_config_word(pcidev, PCI_COMMAND, u16w | 64);
+ /* Enable parity check for parity error */
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
+ s->n_chan = (devpriv->usemux) ? ext_mux : 16;
+ s->maxdata = board->ai_is_16bit ? 0xffff : 0x0fff;
+ s->range_table = board->is_hg ? &pci9118hg_ai_range
+ : &pci9118_ai_range;
+ s->insn_read = pci9118_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = PCI9118_CHANLEN;
+ s->do_cmdtest = pci9118_ai_cmdtest;
+ s->do_cmd = pci9118_ai_cmd;
+ s->cancel = pci9118_ai_cancel;
+ s->munge = pci9118_ai_munge;
+ }
+
+ if (s->maxdata == 0xffff) {
+ /*
+ * 16-bit samples are from an ADS7805 A/D converter.
+ * Minimum sampling rate is 10us.
+ */
+ devpriv->ai_ns_min = 10000;
+ } else {
+ /*
+ * 12-bit samples are from an ADS7800 A/D converter.
+ * Minimum sampling rate is 3us.
+ */
+ devpriv->ai_ns_min = 3000;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = pci9118_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* the analog outputs were reset to 0V, make the readback match */
+ for (i = 0; i < s->n_chan; i++)
+ s->readback[i] = 2047;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci9118_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci9118_do_insn_bits;
+
+ /* get the current state of the digital outputs */
+ s->state = inl(dev->iobase + PCI9118_DIO_REG) >> 4;
+
+ return 0;
+}
+
+static int pci9118_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct pci_dev *pcidev;
+ int ext_mux, softsshdelay;
+
+ ext_mux = it->options[2];
+ softsshdelay = it->options[4];
+
+ pcidev = pci9118_find_pci(dev, it);
+ if (!pcidev)
+ return -EIO;
+ comedi_set_hw_dev(dev, &pcidev->dev);
+
+ return pci9118_common_attach(dev, ext_mux, softsshdelay);
+}
+
+static int pci9118_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct pci9118_boardinfo *board = NULL;
+
+ if (context < ARRAY_SIZE(pci9118_boards))
+ board = &pci9118_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ /*
+ * Need to 'get' the PCI device to match the 'put' in pci9118_detach().
+ * (The 'put' also matches the implicit 'get' by pci9118_find_pci().)
+ */
+ pci_dev_get(pcidev);
+ /* no external mux, no sample-hold delay */
+ return pci9118_common_attach(dev, 0, 0);
+}
+
+static void pci9118_detach(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+ if (dev->iobase)
+ pci9118_reset(dev);
+ comedi_pci_detach(dev);
+ pci9118_free_dma(dev);
+ if (pcidev)
+ pci_dev_put(pcidev);
+}
+
+static struct comedi_driver adl_pci9118_driver = {
+ .driver_name = "adl_pci9118",
+ .module = THIS_MODULE,
+ .attach = pci9118_attach,
+ .auto_attach = pci9118_auto_attach,
+ .detach = pci9118_detach,
+ .num_names = ARRAY_SIZE(pci9118_boards),
+ .board_name = &pci9118_boards[0].name,
+ .offset = sizeof(struct pci9118_boardinfo),
+};
+
+static int adl_pci9118_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci9118_driver,
+ id->driver_data);
+}
+
+/* FIXME: All the supported board types have the same device ID! */
+static const struct pci_device_id adl_pci9118_pci_table[] = {
+ { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118DG },
+/* { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HG }, */
+/* { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HR }, */
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci9118_pci_table);
+
+static struct pci_driver adl_pci9118_pci_driver = {
+ .name = "adl_pci9118",
+ .id_table = adl_pci9118_pci_table,
+ .probe = adl_pci9118_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci9118_driver, adl_pci9118_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adq12b.c b/drivers/staging/comedi/drivers/adq12b.c
new file mode 100644
index 000000000..bc5f97f50
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adq12b.c
@@ -0,0 +1,268 @@
+/*
+ comedi/drivers/adq12b.c
+ driver for MicroAxial ADQ12-B data acquisition and control card
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: adq12b
+Description: driver for MicroAxial ADQ12-B data acquisition and control card
+Devices: [MicroAxial] ADQ12-B (adq12b)
+Author: jeremy theler <thelerg@ib.cnea.gov.ar>
+Updated: Thu, 21 Feb 2008 02:56:27 -0300
+Status: works
+
+Driver for the acquisition card ADQ12-B (without any add-on).
+
+ - Analog input is subdevice 0 (16 channels single-ended or 8 differential)
+ - Digital input is subdevice 1 (5 channels)
+ - Digital output is subdevice 1 (8 channels)
+ - The PACER is not supported in this version
+
+If you do not specify any options, they will default to
+
+ # comedi_config /dev/comedi0 adq12b 0x300,0,0
+
+ option 1: I/O base address. The following table is provided as a help
+ of the hardware jumpers.
+
+ address jumper JADR
+ 0x300 1 (factory default)
+ 0x320 2
+ 0x340 3
+ 0x360 4
+ 0x380 5
+ 0x3A0 6
+
+ option 2: unipolar/bipolar ADC selection: 0 -> bipolar, 1 -> unipolar
+
+ selection comedi_config option JUB
+ bipolar 0 2-3 (factory default)
+ unipolar 1 1-2
+
+ option 3: single-ended/differential AI selection: 0 -> SE, 1 -> differential
+
+ selection comedi_config option JCHA JCHB
+ single-ended 0 1-2 1-2 (factory default)
+ differential 1 2-3 2-3
+
+ written by jeremy theler <thelerg@ib.cnea.gov.ar>
+
+ instituto balseiro
+ commission nacional de energia atomica
+ universidad nacional de cuyo
+ argentina
+
+ 21-feb-2008
+ + changed supported devices string (missused the [] and ())
+
+ 13-oct-2007
+ + first try
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "../comedidev.h"
+
+/* address scheme (page 2.17 of the manual) */
+#define ADQ12B_CTREG 0x00
+#define ADQ12B_CTREG_MSKP (1 << 7) /* enable pacer interrupt */
+#define ADQ12B_CTREG_GTP (1 << 6) /* enable pacer */
+#define ADQ12B_CTREG_RANGE(x) ((x) << 4)
+#define ADQ12B_CTREG_CHAN(x) ((x) << 0)
+#define ADQ12B_STINR 0x00
+#define ADQ12B_STINR_OUT2 (1 << 7) /* timer 2 output state */
+#define ADQ12B_STINR_OUTP (1 << 6) /* pacer output state */
+#define ADQ12B_STINR_EOC (1 << 5) /* A/D end-of-conversion */
+#define ADQ12B_STINR_IN_MASK (0x1f << 0)
+#define ADQ12B_OUTBR 0x04
+#define ADQ12B_ADLOW 0x08
+#define ADQ12B_ADHIG 0x09
+#define ADQ12B_TIMER_BASE 0x0c
+
+/* available ranges through the PGA gains */
+static const struct comedi_lrange range_adq12b_ai_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5)
+ }
+};
+
+static const struct comedi_lrange range_adq12b_ai_unipolar = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5)
+ }
+};
+
+struct adq12b_private {
+ unsigned int last_ctreg;
+};
+
+static int adq12b_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + ADQ12B_STINR);
+ if (status & ADQ12B_STINR_EOC)
+ return 0;
+ return -EBUSY;
+}
+
+static int adq12b_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct adq12b_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int ret;
+ int i;
+
+ /* change channel and range only if it is different from the previous */
+ val = ADQ12B_CTREG_RANGE(range) | ADQ12B_CTREG_CHAN(chan);
+ if (val != devpriv->last_ctreg) {
+ outb(val, dev->iobase + ADQ12B_CTREG);
+ devpriv->last_ctreg = val;
+ udelay(50); /* wait for the mux to settle */
+ }
+
+ val = inb(dev->iobase + ADQ12B_ADLOW); /* trigger A/D */
+
+ for (i = 0; i < insn->n; i++) {
+ ret = comedi_timeout(dev, s, insn, adq12b_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(dev->iobase + ADQ12B_ADHIG) << 8;
+ val |= inb(dev->iobase + ADQ12B_ADLOW); /* retriggers A/D */
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int adq12b_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ /* only bits 0-4 have information about digital inputs */
+ data[1] = (inb(dev->iobase + ADQ12B_STINR) & ADQ12B_STINR_IN_MASK);
+
+ return insn->n;
+}
+
+static int adq12b_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int chan;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ for (chan = 0; chan < 8; chan++) {
+ if ((mask >> chan) & 0x01) {
+ val = (s->state >> chan) & 0x01;
+ outb((val << 3) | chan,
+ dev->iobase + ADQ12B_OUTBR);
+ }
+ }
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int adq12b_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct adq12b_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->last_ctreg = -1; /* force ctreg update */
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ if (it->options[2]) {
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = 8;
+ } else {
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 16;
+ }
+ s->maxdata = 0xfff;
+ s->range_table = it->options[1] ? &range_adq12b_ai_unipolar
+ : &range_adq12b_ai_bipolar;
+ s->insn_read = adq12b_ai_insn_read;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 5;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = adq12b_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = adq12b_do_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver adq12b_driver = {
+ .driver_name = "adq12b",
+ .module = THIS_MODULE,
+ .attach = adq12b_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(adq12b_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adv_pci1710.c b/drivers/staging/comedi/drivers/adv_pci1710.c
new file mode 100644
index 000000000..0c6aa964c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci1710.c
@@ -0,0 +1,1100 @@
+/*
+ * comedi/drivers/adv_pci1710.c
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn>
+ * for testing and information.
+ *
+ * hardware driver for Advantech cards:
+ * card: PCI-1710, PCI-1710HG, PCI-1711, PCI-1713, PCI-1720, PCI-1731
+ * driver: pci1710, pci1710hg, pci1711, pci1713, pci1720, pci1731
+ *
+ * Options:
+ * [0] - PCI bus number - if bus number and slot number are 0,
+ * then driver search for first unused card
+ * [1] - PCI slot number
+ *
+*/
+/*
+Driver: adv_pci1710
+Description: Advantech PCI-1710, PCI-1710HG, PCI-1711, PCI-1713,
+ Advantech PCI-1720, PCI-1731
+Author: Michal Dobes <dobes@tesnet.cz>
+Devices: [Advantech] PCI-1710 (adv_pci1710), PCI-1710HG (pci1710hg),
+ PCI-1711 (adv_pci1710), PCI-1713, PCI-1720,
+ PCI-1731
+Status: works
+
+This driver supports AI, AO, DI and DO subdevices.
+AI subdevice supports cmd and insn interface,
+other subdevices support only insn interface.
+
+The PCI-1710 and PCI-1710HG have the same PCI device ID, so the
+driver cannot distinguish between them, as would be normal for a
+PCI driver.
+
+Configuration options:
+ [0] - PCI bus of device (optional)
+ [1] - PCI slot of device (optional)
+ If bus/slot is not specified, the first available PCI
+ device will be used.
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "amcc_s5933.h"
+
+#define PCI171x_AD_DATA 0 /* R: A/D data */
+#define PCI171x_SOFTTRG 0 /* W: soft trigger for A/D */
+#define PCI171x_RANGE 2 /* W: A/D gain/range register */
+#define PCI171x_MUX 4 /* W: A/D multiplexor control */
+#define PCI171x_STATUS 6 /* R: status register */
+#define PCI171x_CONTROL 6 /* W: control register */
+#define PCI171x_CLRINT 8 /* W: clear interrupts request */
+#define PCI171x_CLRFIFO 9 /* W: clear FIFO */
+#define PCI171x_DA1 10 /* W: D/A register */
+#define PCI171x_DA2 12 /* W: D/A register */
+#define PCI171x_DAREF 14 /* W: D/A reference control */
+#define PCI171x_DI 16 /* R: digi inputs */
+#define PCI171x_DO 16 /* R: digi inputs */
+
+#define PCI171X_TIMER_BASE 0x18
+
+/* upper bits from status register (PCI171x_STATUS) (lower is same with control
+ * reg) */
+#define Status_FE 0x0100 /* 1=FIFO is empty */
+#define Status_FH 0x0200 /* 1=FIFO is half full */
+#define Status_FF 0x0400 /* 1=FIFO is full, fatal error */
+#define Status_IRQ 0x0800 /* 1=IRQ occurred */
+/* bits from control register (PCI171x_CONTROL) */
+#define Control_CNT0 0x0040 /* 1=CNT0 have external source,
+ * 0=have internal 100kHz source */
+#define Control_ONEFH 0x0020 /* 1=IRQ on FIFO is half full, 0=every sample */
+#define Control_IRQEN 0x0010 /* 1=enable IRQ */
+#define Control_GATE 0x0008 /* 1=enable external trigger GATE (8254?) */
+#define Control_EXT 0x0004 /* 1=external trigger source */
+#define Control_PACER 0x0002 /* 1=enable internal 8254 trigger source */
+#define Control_SW 0x0001 /* 1=enable software trigger source */
+
+#define PCI1720_DA0 0 /* W: D/A register 0 */
+#define PCI1720_DA1 2 /* W: D/A register 1 */
+#define PCI1720_DA2 4 /* W: D/A register 2 */
+#define PCI1720_DA3 6 /* W: D/A register 3 */
+#define PCI1720_RANGE 8 /* R/W: D/A range register */
+#define PCI1720_SYNCOUT 9 /* W: D/A synchronized output register */
+#define PCI1720_SYNCONT 15 /* R/W: D/A synchronized control */
+
+/* D/A synchronized control (PCI1720_SYNCONT) */
+#define Syncont_SC0 1 /* set synchronous output mode */
+
+static const struct comedi_lrange range_pci1710_3 = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const char range_codes_pci1710_3[] = { 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x10, 0x11, 0x12, 0x13 };
+
+static const struct comedi_lrange range_pci1710hg = {
+ 12, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.005),
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.01),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01)
+ }
+};
+
+static const char range_codes_pci1710hg[] = { 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x10, 0x11,
+ 0x12, 0x13 };
+
+static const struct comedi_lrange range_pci17x1 = {
+ 5, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const char range_codes_pci17x1[] = { 0x00, 0x01, 0x02, 0x03, 0x04 };
+
+static const struct comedi_lrange pci1720_ao_range = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10)
+ }
+};
+
+static const struct comedi_lrange pci171x_ao_range = {
+ 2, {
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+enum pci1710_boardid {
+ BOARD_PCI1710,
+ BOARD_PCI1710HG,
+ BOARD_PCI1711,
+ BOARD_PCI1713,
+ BOARD_PCI1720,
+ BOARD_PCI1731,
+};
+
+struct boardtype {
+ const char *name; /* board name */
+ int n_aichan; /* num of A/D chans */
+ const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */
+ const char *rangecode_ai; /* range codes for programming */
+ unsigned int is_pci1713:1;
+ unsigned int is_pci1720:1;
+ unsigned int has_irq:1;
+ unsigned int has_large_fifo:1; /* 4K or 1K FIFO */
+ unsigned int has_diff_ai:1;
+ unsigned int has_ao:1;
+ unsigned int has_di_do:1;
+ unsigned int has_counter:1;
+};
+
+static const struct boardtype boardtypes[] = {
+ [BOARD_PCI1710] = {
+ .name = "pci1710",
+ .n_aichan = 16,
+ .rangelist_ai = &range_pci1710_3,
+ .rangecode_ai = range_codes_pci1710_3,
+ .has_irq = 1,
+ .has_large_fifo = 1,
+ .has_diff_ai = 1,
+ .has_ao = 1,
+ .has_di_do = 1,
+ .has_counter = 1,
+ },
+ [BOARD_PCI1710HG] = {
+ .name = "pci1710hg",
+ .n_aichan = 16,
+ .rangelist_ai = &range_pci1710hg,
+ .rangecode_ai = range_codes_pci1710hg,
+ .has_irq = 1,
+ .has_large_fifo = 1,
+ .has_diff_ai = 1,
+ .has_ao = 1,
+ .has_di_do = 1,
+ .has_counter = 1,
+ },
+ [BOARD_PCI1711] = {
+ .name = "pci1711",
+ .n_aichan = 16,
+ .rangelist_ai = &range_pci17x1,
+ .rangecode_ai = range_codes_pci17x1,
+ .has_irq = 1,
+ .has_ao = 1,
+ .has_di_do = 1,
+ .has_counter = 1,
+ },
+ [BOARD_PCI1713] = {
+ .name = "pci1713",
+ .n_aichan = 32,
+ .rangelist_ai = &range_pci1710_3,
+ .rangecode_ai = range_codes_pci1710_3,
+ .is_pci1713 = 1,
+ .has_irq = 1,
+ .has_large_fifo = 1,
+ .has_diff_ai = 1,
+ },
+ [BOARD_PCI1720] = {
+ .name = "pci1720",
+ .is_pci1720 = 1,
+ .has_ao = 1,
+ },
+ [BOARD_PCI1731] = {
+ .name = "pci1731",
+ .n_aichan = 16,
+ .rangelist_ai = &range_pci17x1,
+ .rangecode_ai = range_codes_pci17x1,
+ .has_irq = 1,
+ .has_di_do = 1,
+ },
+};
+
+struct pci1710_private {
+ unsigned int max_samples;
+ unsigned int CntrlReg; /* Control register */
+ unsigned char ai_et;
+ unsigned int ai_et_CntrlReg;
+ unsigned int ai_et_MuxVal;
+ unsigned int act_chanlist[32]; /* list of scanned channel */
+ unsigned char saved_seglen; /* len of the non-repeating chanlist */
+ unsigned char da_ranges; /* copy of D/A outpit range register */
+};
+
+static int pci171x_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int last_aref = CR_AREF(cmd->chanlist[0]);
+ unsigned int next_chan = (chan0 + 1) % s->n_chan;
+ unsigned int chansegment[32];
+ unsigned int seglen;
+ int i;
+
+ if (cmd->chanlist_len == 1) {
+ devpriv->saved_seglen = cmd->chanlist_len;
+ return 0;
+ }
+
+ /* first channel is always ok */
+ chansegment[0] = cmd->chanlist[0];
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (cmd->chanlist[0] == cmd->chanlist[i])
+ break; /* we detected a loop, stop */
+
+ if (aref == AREF_DIFF && (chan & 1)) {
+ dev_err(dev->class_dev,
+ "Odd channel cannot be differential input!\n");
+ return -EINVAL;
+ }
+
+ if (last_aref == AREF_DIFF)
+ next_chan = (next_chan + 1) % s->n_chan;
+ if (chan != next_chan) {
+ dev_err(dev->class_dev,
+ "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
+ i, chan, next_chan, chan0);
+ return -EINVAL;
+ }
+
+ /* next correct channel in list */
+ chansegment[i] = cmd->chanlist[i];
+ last_aref = aref;
+ }
+ seglen = i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ if (cmd->chanlist[i] != chansegment[i % seglen]) {
+ dev_err(dev->class_dev,
+ "bad channel, reference or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
+ i, CR_CHAN(chansegment[i]),
+ CR_RANGE(chansegment[i]),
+ CR_AREF(chansegment[i]),
+ CR_CHAN(cmd->chanlist[i % seglen]),
+ CR_RANGE(cmd->chanlist[i % seglen]),
+ CR_AREF(chansegment[i % seglen]));
+ return -EINVAL;
+ }
+ }
+ devpriv->saved_seglen = seglen;
+
+ return 0;
+}
+
+static void pci171x_ai_setup_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist,
+ unsigned int n_chan,
+ unsigned int seglen)
+{
+ const struct boardtype *board = dev->board_ptr;
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int first_chan = CR_CHAN(chanlist[0]);
+ unsigned int last_chan = CR_CHAN(chanlist[seglen - 1]);
+ unsigned int i;
+
+ for (i = 0; i < seglen; i++) { /* store range list to card */
+ unsigned int chan = CR_CHAN(chanlist[i]);
+ unsigned int range = CR_RANGE(chanlist[i]);
+ unsigned int aref = CR_AREF(chanlist[i]);
+ unsigned int rangeval;
+
+ rangeval = board->rangecode_ai[range];
+ if (aref == AREF_DIFF)
+ rangeval |= 0x0020;
+
+ /* select channel and set range */
+ outw(chan | (chan << 8), dev->iobase + PCI171x_MUX);
+ outw(rangeval, dev->iobase + PCI171x_RANGE);
+
+ devpriv->act_chanlist[i] = chan;
+ }
+ for ( ; i < n_chan; i++) /* store remainder of channel list */
+ devpriv->act_chanlist[i] = CR_CHAN(chanlist[i]);
+
+ /* select channel interval to scan */
+ devpriv->ai_et_MuxVal = first_chan | (last_chan << 8);
+ outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX);
+}
+
+static int pci171x_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + PCI171x_STATUS);
+ if ((status & Status_FE) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci171x_ai_read_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int cur_chan,
+ unsigned int *val)
+{
+ const struct boardtype *board = dev->board_ptr;
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int sample;
+ unsigned int chan;
+
+ sample = inw(dev->iobase + PCI171x_AD_DATA);
+ if (!board->is_pci1713) {
+ /*
+ * The upper 4 bits of the 16-bit sample are the channel number
+ * that the sample was acquired from. Verify that this channel
+ * number matches the expected channel number.
+ */
+ chan = sample >> 12;
+ if (chan != devpriv->act_chanlist[cur_chan]) {
+ dev_err(dev->class_dev,
+ "A/D data droput: received from channel %d, expected %d\n",
+ chan, devpriv->act_chanlist[cur_chan]);
+ return -ENODATA;
+ }
+ }
+ *val = sample & s->maxdata;
+ return 0;
+}
+
+static int pci171x_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci1710_private *devpriv = dev->private;
+ int ret = 0;
+ int i;
+
+ devpriv->CntrlReg &= Control_CNT0;
+ devpriv->CntrlReg |= Control_SW; /* set software trigger */
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+
+ pci171x_ai_setup_chanlist(dev, s, &insn->chanspec, 1, 1);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val;
+
+ outw(0, dev->iobase + PCI171x_SOFTTRG); /* start conversion */
+
+ ret = comedi_timeout(dev, s, insn, pci171x_ai_eoc, 0);
+ if (ret)
+ break;
+
+ ret = pci171x_ai_read_sample(dev, s, 0, &val);
+ if (ret)
+ break;
+
+ data[i] = val;
+ }
+
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+
+ return ret ? ret : insn->n;
+}
+
+static int pci171x_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int reg = chan ? PCI171x_DA2 : PCI171x_DA1;
+ unsigned int val = s->readback[chan];
+ int i;
+
+ devpriv->da_ranges &= ~(1 << (chan << 1));
+ devpriv->da_ranges |= (range << (chan << 1));
+ outw(devpriv->da_ranges, dev->iobase + PCI171x_DAREF);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, dev->iobase + reg);
+ }
+
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pci171x_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inw(dev->iobase + PCI171x_DI);
+
+ return insn->n;
+}
+
+static int pci171x_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI171x_DO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci1720_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ val = devpriv->da_ranges & (~(0x03 << (chan << 1)));
+ val |= (range << (chan << 1));
+ if (val != devpriv->da_ranges) {
+ outb(val, dev->iobase + PCI1720_RANGE);
+ devpriv->da_ranges = val;
+ }
+
+ val = s->readback[chan];
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, dev->iobase + PCI1720_DA0 + (chan << 1));
+ outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */
+ }
+
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pci171x_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci1710_private *devpriv = dev->private;
+
+ devpriv->CntrlReg &= Control_CNT0;
+ devpriv->CntrlReg |= Control_SW;
+ /* reset any operations */
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+
+ return 0;
+}
+
+static void pci1710_handle_every_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int status;
+ unsigned int val;
+ int ret;
+
+ status = inw(dev->iobase + PCI171x_STATUS);
+ if (status & Status_FE) {
+ dev_dbg(dev->class_dev, "A/D FIFO empty (%4x)\n", status);
+ s->async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+ if (status & Status_FF) {
+ dev_dbg(dev->class_dev,
+ "A/D FIFO Full status (Fatal Error!) (%4x)\n", status);
+ s->async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */
+
+ for (; !(inw(dev->iobase + PCI171x_STATUS) & Status_FE);) {
+ ret = pci171x_ai_read_sample(dev, s, s->async->cur_chan, &val);
+ if (ret) {
+ s->async->events |= COMEDI_CB_ERROR;
+ break;
+ }
+
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg) {
+ s->async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */
+}
+
+static void pci1710_handle_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int status;
+ int i;
+
+ status = inw(dev->iobase + PCI171x_STATUS);
+ if (!(status & Status_FH)) {
+ dev_dbg(dev->class_dev, "A/D FIFO not half full!\n");
+ async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+ if (status & Status_FF) {
+ dev_dbg(dev->class_dev,
+ "A/D FIFO Full status (Fatal Error!)\n");
+ async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+
+ for (i = 0; i < devpriv->max_samples; i++) {
+ unsigned int val;
+ int ret;
+
+ ret = pci171x_ai_read_sample(dev, s, s->async->cur_chan, &val);
+ if (ret) {
+ s->async->events |= COMEDI_CB_ERROR;
+ break;
+ }
+
+ if (!comedi_buf_write_samples(s, &val, 1))
+ break;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */
+}
+
+static irqreturn_t interrupt_service_pci1710(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ struct comedi_cmd *cmd;
+
+ if (!dev->attached) /* is device attached? */
+ return IRQ_NONE; /* no, exit */
+
+ s = dev->read_subdev;
+ cmd = &s->async->cmd;
+
+ /* is this interrupt from our board? */
+ if (!(inw(dev->iobase + PCI171x_STATUS) & Status_IRQ))
+ return IRQ_NONE; /* no, exit */
+
+ if (devpriv->ai_et) { /* Switch from initial TRIG_EXT to TRIG_xxx. */
+ devpriv->ai_et = 0;
+ devpriv->CntrlReg &= Control_CNT0;
+ devpriv->CntrlReg |= Control_SW; /* set software trigger */
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+ devpriv->CntrlReg = devpriv->ai_et_CntrlReg;
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+ outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX);
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ return IRQ_HANDLED;
+ }
+
+ if (cmd->flags & CMDF_WAKE_EOS)
+ pci1710_handle_every_sample(dev, s);
+ else
+ pci1710_handle_fifo(dev, s);
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int pci171x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ pci171x_ai_setup_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len,
+ devpriv->saved_seglen);
+
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+
+ devpriv->CntrlReg &= Control_CNT0;
+ if ((cmd->flags & CMDF_WAKE_EOS) == 0)
+ devpriv->CntrlReg |= Control_ONEFH;
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(dev->pacer);
+
+ devpriv->CntrlReg |= Control_PACER | Control_IRQEN;
+ if (cmd->start_src == TRIG_EXT) {
+ devpriv->ai_et_CntrlReg = devpriv->CntrlReg;
+ devpriv->CntrlReg &=
+ ~(Control_PACER | Control_ONEFH | Control_GATE);
+ devpriv->CntrlReg |= Control_EXT;
+ devpriv->ai_et = 1;
+ } else { /* TRIG_NOW */
+ devpriv->ai_et = 0;
+ }
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+
+ if (cmd->start_src == TRIG_NOW)
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ } else { /* TRIG_EXT */
+ devpriv->CntrlReg |= Control_EXT | Control_IRQEN;
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+ }
+
+ return 0;
+}
+
+static int pci171x_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* step 2a: make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* step 2b: and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
+ else /* TRIG_FOLLOW */
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ unsigned int arg = cmd->convert_arg;
+
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list */
+
+ err |= pci171x_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int pci171x_insn_counter_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci1710_private *devpriv = dev->private;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ switch (data[1]) {
+ case 0: /* internal */
+ devpriv->ai_et_CntrlReg &= ~Control_CNT0;
+ break;
+ case 1: /* external */
+ devpriv->ai_et_CntrlReg |= Control_CNT0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ outw(devpriv->ai_et_CntrlReg, dev->iobase + PCI171x_CONTROL);
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ if (devpriv->ai_et_CntrlReg & Control_CNT0) {
+ data[1] = 1;
+ data[2] = 0;
+ } else {
+ data[1] = 0;
+ data[2] = I8254_OSC_BASE_10MHZ;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int pci171x_reset(struct comedi_device *dev)
+{
+ const struct boardtype *board = dev->board_ptr;
+ struct pci1710_private *devpriv = dev->private;
+
+ /* Software trigger, CNT0=external */
+ devpriv->CntrlReg = Control_SW | Control_CNT0;
+ /* reset any operations */
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+ outb(0, dev->iobase + PCI171x_CLRFIFO); /* clear FIFO */
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear INT request */
+ devpriv->da_ranges = 0;
+ if (board->has_ao) {
+ /* set DACs to 0..5V */
+ outb(devpriv->da_ranges, dev->iobase + PCI171x_DAREF);
+ outw(0, dev->iobase + PCI171x_DA1); /* set DA outputs to 0V */
+ outw(0, dev->iobase + PCI171x_DA2);
+ }
+ outw(0, dev->iobase + PCI171x_DO); /* digital outputs to 0 */
+ outb(0, dev->iobase + PCI171x_CLRFIFO); /* clear FIFO */
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear INT request */
+
+ return 0;
+}
+
+static int pci1720_reset(struct comedi_device *dev)
+{
+ struct pci1710_private *devpriv = dev->private;
+ /* set synchronous output mode */
+ outb(Syncont_SC0, dev->iobase + PCI1720_SYNCONT);
+ devpriv->da_ranges = 0xAA;
+ /* set all ranges to +/-5V */
+ outb(devpriv->da_ranges, dev->iobase + PCI1720_RANGE);
+ outw(0x0800, dev->iobase + PCI1720_DA0); /* set outputs to 0V */
+ outw(0x0800, dev->iobase + PCI1720_DA1);
+ outw(0x0800, dev->iobase + PCI1720_DA2);
+ outw(0x0800, dev->iobase + PCI1720_DA3);
+ outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */
+
+ return 0;
+}
+
+static int pci1710_reset(struct comedi_device *dev)
+{
+ const struct boardtype *board = dev->board_ptr;
+
+ if (board->is_pci1720)
+ return pci1720_reset(dev);
+
+ return pci171x_reset(dev);
+}
+
+static int pci1710_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct boardtype *board = NULL;
+ struct pci1710_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret, subdev, n_subdevices;
+
+ if (context < ARRAY_SIZE(boardtypes))
+ board = &boardtypes[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ dev->pacer = comedi_8254_init(dev->iobase + PCI171X_TIMER_BASE,
+ I8254_OSC_BASE_10MHZ, I8254_IO16, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ n_subdevices = 0;
+ if (board->n_aichan)
+ n_subdevices++;
+ if (board->has_ao)
+ n_subdevices++;
+ if (board->has_di_do)
+ n_subdevices += 2;
+ if (board->has_counter)
+ n_subdevices++;
+
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ pci1710_reset(dev);
+
+ if (board->has_irq && pcidev->irq) {
+ ret = request_irq(pcidev->irq, interrupt_service_pci1710,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ subdev = 0;
+
+ if (board->n_aichan) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND;
+ if (board->has_diff_ai)
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = board->n_aichan;
+ s->maxdata = 0x0fff;
+ s->range_table = board->rangelist_ai;
+ s->insn_read = pci171x_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = pci171x_ai_cmdtest;
+ s->do_cmd = pci171x_ai_cmd;
+ s->cancel = pci171x_ai_cancel;
+ }
+ subdev++;
+ }
+
+ if (board->has_ao) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->maxdata = 0x0fff;
+ if (board->is_pci1720) {
+ s->n_chan = 4;
+ s->range_table = &pci1720_ao_range;
+ s->insn_write = pci1720_ao_insn_write;
+ } else {
+ s->n_chan = 2;
+ s->range_table = &pci171x_ao_range;
+ s->insn_write = pci171x_ao_insn_write;
+ }
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* initialize the readback values to match the board reset */
+ if (board->is_pci1720) {
+ int i;
+
+ for (i = 0; i < s->n_chan; i++)
+ s->readback[i] = 0x0800;
+ }
+
+ subdev++;
+ }
+
+ if (board->has_di_do) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci171x_di_insn_bits;
+ subdev++;
+
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci171x_do_insn_bits;
+ subdev++;
+ }
+
+ /* Counter subdevice (8254) */
+ if (board->has_counter) {
+ s = &dev->subdevices[subdev];
+ comedi_8254_subdevice_init(s, dev->pacer);
+
+ dev->pacer->insn_config = pci171x_insn_counter_config;
+
+ /* counters 1 and 2 are used internally for the pacer */
+ comedi_8254_set_busy(dev->pacer, 1, true);
+ comedi_8254_set_busy(dev->pacer, 2, true);
+
+ subdev++;
+ }
+
+ /* max_samples is half the FIFO size (2 bytes/sample) */
+ devpriv->max_samples = (board->has_large_fifo) ? 2048 : 512;
+
+ return 0;
+}
+
+static void pci1710_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ pci1710_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver adv_pci1710_driver = {
+ .driver_name = "adv_pci1710",
+ .module = THIS_MODULE,
+ .auto_attach = pci1710_auto_attach,
+ .detach = pci1710_detach,
+};
+
+static int adv_pci1710_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adv_pci1710_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adv_pci1710_pci_table[] = {
+ {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0x0000),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb100),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb200),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc100),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc200),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd100),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0x0002),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb102),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb202),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc102),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc202),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd102),
+ .driver_data = BOARD_PCI1710HG,
+ },
+ { PCI_VDEVICE(ADVANTECH, 0x1711), BOARD_PCI1711 },
+ { PCI_VDEVICE(ADVANTECH, 0x1713), BOARD_PCI1713 },
+ { PCI_VDEVICE(ADVANTECH, 0x1720), BOARD_PCI1720 },
+ { PCI_VDEVICE(ADVANTECH, 0x1731), BOARD_PCI1731 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1710_pci_table);
+
+static struct pci_driver adv_pci1710_pci_driver = {
+ .name = "adv_pci1710",
+ .id_table = adv_pci1710_pci_table,
+ .probe = adv_pci1710_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci1710_driver, adv_pci1710_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi: Advantech PCI-1710 Series Multifunction DAS Cards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adv_pci1723.c b/drivers/staging/comedi/drivers/adv_pci1723.c
new file mode 100644
index 000000000..1921a97cc
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci1723.c
@@ -0,0 +1,233 @@
+/*
+ * adv_pci1723.c
+ * Comedi driver for the Advantech PCI-1723 card.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: adv_pci1723
+ * Description: Advantech PCI-1723
+ * Author: yonggang <rsmgnu@gmail.com>, Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Advantech] PCI-1723 (adv_pci1723)
+ * Updated: Mon, 14 Apr 2008 15:12:56 +0100
+ * Status: works
+ *
+ * Configuration Options: not applicable, uses comedi PCI auto config
+ *
+ * Subdevice 0 is 8-channel AO, 16-bit, range +/- 10 V.
+ *
+ * Subdevice 1 is 16-channel DIO. The channels are configurable as
+ * input or output in 2 groups (0 to 7, 8 to 15). Configuring any
+ * channel implicitly configures all channels in the same group.
+ *
+ * TODO:
+ * 1. Add the two milliamp ranges to the AO subdevice (0 to 20 mA,
+ * 4 to 20 mA).
+ * 2. Read the initial ranges and values of the AO subdevice at
+ * start-up instead of reinitializing them.
+ * 3. Implement calibration.
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * PCI Bar 2 I/O Register map (dev->iobase)
+ */
+#define PCI1723_AO_REG(x) (0x00 + ((x) * 2))
+#define PCI1723_BOARD_ID_REG 0x10
+#define PCI1723_BOARD_ID_MASK (0xf << 0)
+#define PCI1723_SYNC_CTRL_REG 0x12
+#define PCI1723_SYNC_CTRL_ASYNC (0 << 0)
+#define PCI1723_SYNC_CTRL_SYNC (1 << 0)
+#define PCI1723_CTRL_REG 0x14
+#define PCI1723_CTRL_BUSY (1 << 15)
+#define PCI1723_CTRL_INIT (1 << 14)
+#define PCI1723_CTRL_SELF (1 << 8)
+#define PCI1723_CTRL_IDX(x) (((x) & 0x3) << 6)
+#define PCI1723_CTRL_RANGE(x) (((x) & 0x3) << 4)
+#define PCI1723_CTRL_GAIN (0 << 3)
+#define PCI1723_CTRL_OFFSET (1 << 3)
+#define PCI1723_CTRL_CHAN(x) (((x) & 0x7) << 0)
+#define PCI1723_CALIB_CTRL_REG 0x16
+#define PCI1723_CALIB_CTRL_CS (1 << 2)
+#define PCI1723_CALIB_CTRL_DAT (1 << 1)
+#define PCI1723_CALIB_CTRL_CLK (1 << 0)
+#define PCI1723_CALIB_STROBE_REG 0x18
+#define PCI1723_DIO_CTRL_REG 0x1a
+#define PCI1723_DIO_CTRL_HDIO (1 << 1)
+#define PCI1723_DIO_CTRL_LDIO (1 << 0)
+#define PCI1723_DIO_DATA_REG 0x1c
+#define PCI1723_CALIB_DATA_REG 0x1e
+#define PCI1723_SYNC_STROBE_REG 0x20
+#define PCI1723_RESET_AO_STROBE_REG 0x22
+#define PCI1723_RESET_CALIB_STROBE_REG 0x24
+#define PCI1723_RANGE_STROBE_REG 0x26
+#define PCI1723_VREF_REG 0x28
+#define PCI1723_VREF_NEG10V (0 << 0)
+#define PCI1723_VREF_0V (1 << 0)
+#define PCI1723_VREF_POS10V (3 << 0)
+
+static int pci1723_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ outw(val, dev->iobase + PCI1723_AO_REG(chan));
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int pci1723_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask = (chan < 8) ? 0x00ff : 0xff00;
+ unsigned short mode = 0x0000; /* assume output */
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ if (!(s->io_bits & 0x00ff))
+ mode |= PCI1723_DIO_CTRL_LDIO; /* low byte input */
+ if (!(s->io_bits & 0xff00))
+ mode |= PCI1723_DIO_CTRL_HDIO; /* high byte input */
+ outw(mode, dev->iobase + PCI1723_DIO_CTRL_REG);
+
+ return insn->n;
+}
+
+static int pci1723_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI1723_DIO_DATA_REG);
+
+ data[1] = inw(dev->iobase + PCI1723_DIO_DATA_REG);
+
+ return insn->n;
+}
+
+static int pci1723_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ unsigned int val;
+ int ret;
+ int i;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 8;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = pci1723_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* synchronously reset all analog outputs to 0V, +/-10V range */
+ outw(PCI1723_SYNC_CTRL_SYNC, dev->iobase + PCI1723_SYNC_CTRL_REG);
+ for (i = 0; i < s->n_chan; i++) {
+ outw(PCI1723_CTRL_RANGE(0) | PCI1723_CTRL_CHAN(i),
+ PCI1723_CTRL_REG);
+ outw(0, dev->iobase + PCI1723_RANGE_STROBE_REG);
+
+ outw(0x8000, dev->iobase + PCI1723_AO_REG(i));
+ s->readback[i] = 0x8000;
+ }
+ outw(0, dev->iobase + PCI1723_SYNC_STROBE_REG);
+
+ /* disable syncronous control */
+ outw(PCI1723_SYNC_CTRL_ASYNC, dev->iobase + PCI1723_SYNC_CTRL_REG);
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = pci1723_dio_insn_config;
+ s->insn_bits = pci1723_dio_insn_bits;
+
+ /* get initial DIO direction and state */
+ val = inw(dev->iobase + PCI1723_DIO_CTRL_REG);
+ if (!(val & PCI1723_DIO_CTRL_LDIO))
+ s->io_bits |= 0x00ff; /* low byte output */
+ if (!(val & PCI1723_DIO_CTRL_HDIO))
+ s->io_bits |= 0xff00; /* high byte output */
+ s->state = inw(dev->iobase + PCI1723_DIO_DATA_REG);
+
+ return 0;
+}
+
+static struct comedi_driver adv_pci1723_driver = {
+ .driver_name = "adv_pci1723",
+ .module = THIS_MODULE,
+ .auto_attach = pci1723_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int adv_pci1723_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adv_pci1723_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adv_pci1723_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1723) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1723_pci_table);
+
+static struct pci_driver adv_pci1723_pci_driver = {
+ .name = "adv_pci1723",
+ .id_table = adv_pci1723_pci_table,
+ .probe = adv_pci1723_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci1723_driver, adv_pci1723_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Advantech PCI-1723 Comedi driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adv_pci1724.c b/drivers/staging/comedi/drivers/adv_pci1724.c
new file mode 100644
index 000000000..f7a7dab01
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci1724.c
@@ -0,0 +1,220 @@
+/*
+ * adv_pci1724.c
+ * Comedi driver for the Advantech PCI-1724U card.
+ *
+ * Author: Frank Mori Hess <fmh6jj@gmail.com>
+ * Copyright (C) 2013 GnuBIO Inc
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: adv_pci1724
+ * Description: Advantech PCI-1724U
+ * Devices: [Advantech] PCI-1724U (adv_pci1724)
+ * Author: Frank Mori Hess <fmh6jj@gmail.com>
+ * Updated: 2013-02-09
+ * Status: works
+ *
+ * Configuration Options: not applicable, uses comedi PCI auto config
+ *
+ * Subdevice 0 is the analog output.
+ * Subdevice 1 is the offset calibration for the analog output.
+ * Subdevice 2 is the gain calibration for the analog output.
+ *
+ * The calibration offset and gains have quite a large effect on the
+ * analog output, so it is possible to adjust the analog output to
+ * have an output range significantly different from the board's
+ * nominal output ranges. For a calibrated +/-10V range, the analog
+ * output's offset will be set somewhere near mid-range (0x2000) and
+ * its gain will be near maximum (0x3fff).
+ *
+ * There is really no difference between the board's documented 0-20mA
+ * versus 4-20mA output ranges. To pick one or the other is simply a
+ * matter of adjusting the offset and gain calibration until the board
+ * outputs in the desired range.
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * PCI bar 2 Register I/O map (dev->iobase)
+ */
+#define PCI1724_DAC_CTRL_REG 0x00
+#define PCI1724_DAC_CTRL_GX(x) (1 << (20 + ((x) / 8)))
+#define PCI1724_DAC_CTRL_CX(x) (((x) % 8) << 16)
+#define PCI1724_DAC_CTRL_MODE_GAIN (1 << 14)
+#define PCI1724_DAC_CTRL_MODE_OFFSET (2 << 14)
+#define PCI1724_DAC_CTRL_MODE_NORMAL (3 << 14)
+#define PCI1724_DAC_CTRL_MODE_MASK (3 << 14)
+#define PCI1724_DAC_CTRL_DATA(x) (((x) & 0x3fff) << 0)
+#define PCI1724_SYNC_CTRL_REG 0x04
+#define PCI1724_SYNC_CTRL_DACSTAT (1 << 1)
+#define PCI1724_SYNC_CTRL_SYN (1 << 0)
+#define PCI1724_EEPROM_CTRL_REG 0x08
+#define PCI1724_SYNC_TRIG_REG 0x0c /* any value works */
+#define PCI1724_BOARD_ID_REG 0x10
+#define PCI1724_BOARD_ID_MASK (0xf << 0)
+
+static const struct comedi_lrange adv_pci1724_ao_ranges = {
+ 4, {
+ BIP_RANGE(10),
+ RANGE_mA(0, 20),
+ RANGE_mA(4, 20),
+ RANGE_unitless(0, 1)
+ }
+};
+
+static int adv_pci1724_dac_idle(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inl(dev->iobase + PCI1724_SYNC_CTRL_REG);
+ if ((status & PCI1724_SYNC_CTRL_DACSTAT) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int adv_pci1724_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long mode = (unsigned long)s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int ctrl;
+ int ret;
+ int i;
+
+ ctrl = PCI1724_DAC_CTRL_GX(chan) | PCI1724_DAC_CTRL_CX(chan) | mode;
+
+ /* turn off synchronous mode */
+ outl(0, dev->iobase + PCI1724_SYNC_CTRL_REG);
+
+ for (i = 0; i < insn->n; ++i) {
+ unsigned int val = data[i];
+
+ ret = comedi_timeout(dev, s, insn, adv_pci1724_dac_idle, 0);
+ if (ret)
+ return ret;
+
+ outl(ctrl | PCI1724_DAC_CTRL_DATA(val),
+ dev->iobase + PCI1724_DAC_CTRL_REG);
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int adv_pci1724_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ unsigned int board_id;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 2);
+ board_id = inl(dev->iobase + PCI1724_BOARD_ID_REG);
+ dev_info(dev->class_dev, "board id: %d\n",
+ board_id & PCI1724_BOARD_ID_MASK);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = 32;
+ s->maxdata = 0x3fff;
+ s->range_table = &adv_pci1724_ao_ranges;
+ s->insn_write = adv_pci1724_insn_write;
+ s->private = (void *)PCI1724_DAC_CTRL_MODE_NORMAL;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Offset Calibration subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = 32;
+ s->maxdata = 0x3fff;
+ s->insn_write = adv_pci1724_insn_write;
+ s->private = (void *)PCI1724_DAC_CTRL_MODE_OFFSET;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Gain Calibration subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = 32;
+ s->maxdata = 0x3fff;
+ s->insn_write = adv_pci1724_insn_write;
+ s->private = (void *)PCI1724_DAC_CTRL_MODE_GAIN;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver adv_pci1724_driver = {
+ .driver_name = "adv_pci1724",
+ .module = THIS_MODULE,
+ .auto_attach = adv_pci1724_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int adv_pci1724_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adv_pci1724_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adv_pci1724_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table);
+
+static struct pci_driver adv_pci1724_pci_driver = {
+ .name = "adv_pci1724",
+ .id_table = adv_pci1724_pci_table,
+ .probe = adv_pci1724_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver);
+
+MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>");
+MODULE_DESCRIPTION("Advantech PCI-1724U Comedi driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adv_pci_dio.c b/drivers/staging/comedi/drivers/adv_pci_dio.c
new file mode 100644
index 000000000..456e87013
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci_dio.c
@@ -0,0 +1,1113 @@
+/*
+ * comedi/drivers/adv_pci_dio.c
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ * Hardware driver for Advantech PCI DIO cards.
+*/
+/*
+Driver: adv_pci_dio
+Description: Advantech PCI-1730, PCI-1733, PCI-1734, PCI-1735U,
+ PCI-1736UP, PCI-1739U, PCI-1750, PCI-1751, PCI-1752,
+ PCI-1753/E, PCI-1754, PCI-1756, PCI-1760, PCI-1762
+Author: Michal Dobes <dobes@tesnet.cz>
+Devices: [Advantech] PCI-1730 (adv_pci_dio), PCI-1733,
+ PCI-1734, PCI-1735U, PCI-1736UP, PCI-1739U, PCI-1750,
+ PCI-1751, PCI-1752, PCI-1753,
+ PCI-1753+PCI-1753E, PCI-1754, PCI-1756,
+ PCI-1760, PCI-1762
+Status: untested
+Updated: Mon, 09 Jan 2012 12:40:46 +0000
+
+This driver supports now only insn interface for DI/DO/DIO.
+
+Configuration options:
+ [0] - PCI bus of device (optional)
+ [1] - PCI slot of device (optional)
+ If bus/slot is not specified, the first available PCI
+ device will be used.
+
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "../comedi_pci.h"
+
+#include "8255.h"
+#include "comedi_8254.h"
+
+/* hardware types of the cards */
+enum hw_cards_id {
+ TYPE_PCI1730, TYPE_PCI1733, TYPE_PCI1734, TYPE_PCI1735, TYPE_PCI1736,
+ TYPE_PCI1739,
+ TYPE_PCI1750,
+ TYPE_PCI1751,
+ TYPE_PCI1752,
+ TYPE_PCI1753, TYPE_PCI1753E,
+ TYPE_PCI1754, TYPE_PCI1756,
+ TYPE_PCI1760,
+ TYPE_PCI1762
+};
+
+/* which I/O instructions to use */
+enum hw_io_access {
+ IO_8b, IO_16b
+};
+
+#define MAX_DI_SUBDEVS 2 /* max number of DI subdevices per card */
+#define MAX_DO_SUBDEVS 2 /* max number of DO subdevices per card */
+#define MAX_DIO_SUBDEVG 2 /* max number of DIO subdevices group per
+ * card */
+
+#define PCIDIO_MAINREG 2 /* main I/O region for all Advantech cards? */
+
+/* Register offset definitions */
+/* Advantech PCI-1730/3/4 */
+#define PCI1730_IDI 0 /* R: Isolated digital input 0-15 */
+#define PCI1730_IDO 0 /* W: Isolated digital output 0-15 */
+#define PCI1730_DI 2 /* R: Digital input 0-15 */
+#define PCI1730_DO 2 /* W: Digital output 0-15 */
+#define PCI1733_IDI 0 /* R: Isolated digital input 0-31 */
+#define PCI1730_3_INT_EN 0x08 /* R/W: enable/disable interrupts */
+#define PCI1730_3_INT_RF 0x0c /* R/W: set falling/raising edge for
+ * interrupts */
+#define PCI1730_3_INT_CLR 0x10 /* R/W: clear interrupts */
+#define PCI1734_IDO 0 /* W: Isolated digital output 0-31 */
+#define PCI173x_BOARDID 4 /* R: Board I/D switch for 1730/3/4 */
+
+/* Advantech PCI-1735U */
+#define PCI1735_DI 0 /* R: Digital input 0-31 */
+#define PCI1735_DO 0 /* W: Digital output 0-31 */
+#define PCI1735_C8254 4 /* R/W: 8254 counter */
+#define PCI1735_BOARDID 8 /* R: Board I/D switch for 1735U */
+
+/* Advantech PCI-1736UP */
+#define PCI1736_IDI 0 /* R: Isolated digital input 0-15 */
+#define PCI1736_IDO 0 /* W: Isolated digital output 0-15 */
+#define PCI1736_3_INT_EN 0x08 /* R/W: enable/disable interrupts */
+#define PCI1736_3_INT_RF 0x0c /* R/W: set falling/raising edge for
+ * interrupts */
+#define PCI1736_3_INT_CLR 0x10 /* R/W: clear interrupts */
+#define PCI1736_BOARDID 4 /* R: Board I/D switch for 1736UP */
+#define PCI1736_MAINREG 0 /* Normal register (2) doesn't work */
+
+/* Advantech PCI-1739U */
+#define PCI1739_DIO 0 /* R/W: begin of 8255 registers block */
+#define PCI1739_ICR 32 /* W: Interrupt control register */
+#define PCI1739_ISR 32 /* R: Interrupt status register */
+#define PCI1739_BOARDID 8 /* R: Board I/D switch for 1739U */
+
+/* Advantech PCI-1750 */
+#define PCI1750_IDI 0 /* R: Isolated digital input 0-15 */
+#define PCI1750_IDO 0 /* W: Isolated digital output 0-15 */
+#define PCI1750_ICR 32 /* W: Interrupt control register */
+#define PCI1750_ISR 32 /* R: Interrupt status register */
+
+/* Advantech PCI-1751/3/3E */
+#define PCI1751_DIO 0 /* R/W: begin of 8255 registers block */
+#define PCI1751_CNT 24 /* R/W: begin of 8254 registers block */
+#define PCI1751_ICR 32 /* W: Interrupt control register */
+#define PCI1751_ISR 32 /* R: Interrupt status register */
+#define PCI1753_DIO 0 /* R/W: begin of 8255 registers block */
+#define PCI1753_ICR0 16 /* R/W: Interrupt control register group 0 */
+#define PCI1753_ICR1 17 /* R/W: Interrupt control register group 1 */
+#define PCI1753_ICR2 18 /* R/W: Interrupt control register group 2 */
+#define PCI1753_ICR3 19 /* R/W: Interrupt control register group 3 */
+#define PCI1753E_DIO 32 /* R/W: begin of 8255 registers block */
+#define PCI1753E_ICR0 48 /* R/W: Interrupt control register group 0 */
+#define PCI1753E_ICR1 49 /* R/W: Interrupt control register group 1 */
+#define PCI1753E_ICR2 50 /* R/W: Interrupt control register group 2 */
+#define PCI1753E_ICR3 51 /* R/W: Interrupt control register group 3 */
+
+/* Advantech PCI-1752/4/6 */
+#define PCI1752_IDO 0 /* R/W: Digital output 0-31 */
+#define PCI1752_IDO2 4 /* R/W: Digital output 32-63 */
+#define PCI1754_IDI 0 /* R: Digital input 0-31 */
+#define PCI1754_IDI2 4 /* R: Digital input 32-64 */
+#define PCI1756_IDI 0 /* R: Digital input 0-31 */
+#define PCI1756_IDO 4 /* R/W: Digital output 0-31 */
+#define PCI1754_6_ICR0 0x08 /* R/W: Interrupt control register group 0 */
+#define PCI1754_6_ICR1 0x0a /* R/W: Interrupt control register group 1 */
+#define PCI1754_ICR2 0x0c /* R/W: Interrupt control register group 2 */
+#define PCI1754_ICR3 0x0e /* R/W: Interrupt control register group 3 */
+#define PCI1752_6_CFC 0x12 /* R/W: set/read channel freeze function */
+#define PCI175x_BOARDID 0x10 /* R: Board I/D switch for 1752/4/6 */
+
+/* Advantech PCI-1762 registers */
+#define PCI1762_RO 0 /* R/W: Relays status/output */
+#define PCI1762_IDI 2 /* R: Isolated input status */
+#define PCI1762_BOARDID 4 /* R: Board I/D switch */
+#define PCI1762_ICR 6 /* W: Interrupt control register */
+#define PCI1762_ISR 6 /* R: Interrupt status register */
+
+/* Advantech PCI-1760 registers */
+#define OMB0 0x0c /* W: Mailbox outgoing registers */
+#define OMB1 0x0d
+#define OMB2 0x0e
+#define OMB3 0x0f
+#define IMB0 0x1c /* R: Mailbox incoming registers */
+#define IMB1 0x1d
+#define IMB2 0x1e
+#define IMB3 0x1f
+#define INTCSR0 0x38 /* R/W: Interrupt control registers */
+#define INTCSR1 0x39
+#define INTCSR2 0x3a
+#define INTCSR3 0x3b
+
+/* PCI-1760 mailbox commands */
+#define CMD_ClearIMB2 0x00 /* Clear IMB2 status and return actual
+ * DI status in IMB3 */
+#define CMD_SetRelaysOutput 0x01 /* Set relay output from OMB0 */
+#define CMD_GetRelaysStatus 0x02 /* Get relay status to IMB0 */
+#define CMD_ReadCurrentStatus 0x07 /* Read the current status of the
+ * register in OMB0, result in IMB0 */
+#define CMD_ReadFirmwareVersion 0x0e /* Read the firmware ver., result in
+ * IMB1.IMB0 */
+#define CMD_ReadHardwareVersion 0x0f /* Read the hardware ver., result in
+ * IMB1.IMB0 */
+#define CMD_EnableIDIFilters 0x20 /* Enable IDI filters based on bits in
+ * OMB0 */
+#define CMD_EnableIDIPatternMatch 0x21 /* Enable IDI pattern match based on
+ * bits in OMB0 */
+#define CMD_SetIDIPatternMatch 0x22 /* Enable IDI pattern match based on
+ * bits in OMB0 */
+#define CMD_EnableIDICounters 0x28 /* Enable IDI counters based on bits in
+ * OMB0 */
+#define CMD_ResetIDICounters 0x29 /* Reset IDI counters based on bits in
+ * OMB0 to its reset values */
+#define CMD_OverflowIDICounters 0x2a /* Enable IDI counters overflow
+ * interrupts based on bits in OMB0 */
+#define CMD_MatchIntIDICounters 0x2b /* Enable IDI counters match value
+ * interrupts based on bits in OMB0 */
+#define CMD_EdgeIDICounters 0x2c /* Set IDI up counters count edge (bit=0
+ * - rising, =1 - falling) */
+#define CMD_GetIDICntCurValue 0x2f /* Read IDI{OMB0} up counter current
+ * value */
+#define CMD_SetIDI0CntResetValue 0x40 /* Set IDI0 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI1CntResetValue 0x41 /* Set IDI1 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI2CntResetValue 0x42 /* Set IDI2 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI3CntResetValue 0x43 /* Set IDI3 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI4CntResetValue 0x44 /* Set IDI4 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI5CntResetValue 0x45 /* Set IDI5 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI6CntResetValue 0x46 /* Set IDI6 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI7CntResetValue 0x47 /* Set IDI7 Counter Reset Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI0CntMatchValue 0x48 /* Set IDI0 Counter Match Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI1CntMatchValue 0x49 /* Set IDI1 Counter Match Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI2CntMatchValue 0x4a /* Set IDI2 Counter Match Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI3CntMatchValue 0x4b /* Set IDI3 Counter Match Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI4CntMatchValue 0x4c /* Set IDI4 Counter Match Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI5CntMatchValue 0x4d /* Set IDI5 Counter Match Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI6CntMatchValue 0x4e /* Set IDI6 Counter Match Value
+ * 256*OMB1+OMB0 */
+#define CMD_SetIDI7CntMatchValue 0x4f /* Set IDI7 Counter Match Value
+ * 256*OMB1+OMB0 */
+
+#define OMBCMD_RETRY 0x03 /* 3 times try request before error */
+
+struct diosubd_data {
+ int chans; /* num of chans */
+ int addr; /* PCI address ofset */
+ int regs; /* number of registers to read or 8255
+ subdevices */
+ unsigned int specflags; /* addon subdevice flags */
+};
+
+struct dio_boardtype {
+ const char *name; /* board name */
+ int main_pci_region; /* main I/O PCI region */
+ enum hw_cards_id cardtype;
+ int nsubdevs;
+ struct diosubd_data sdi[MAX_DI_SUBDEVS]; /* DI chans */
+ struct diosubd_data sdo[MAX_DO_SUBDEVS]; /* DO chans */
+ struct diosubd_data sdio[MAX_DIO_SUBDEVG]; /* DIO 8255 chans */
+ struct diosubd_data boardid; /* card supports board ID switch */
+ unsigned long timer_regbase;
+ enum hw_io_access io_access;
+};
+
+static const struct dio_boardtype boardtypes[] = {
+ [TYPE_PCI1730] = {
+ .name = "pci1730",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1730,
+ .nsubdevs = 5,
+ .sdi[0] = { 16, PCI1730_DI, 2, 0, },
+ .sdi[1] = { 16, PCI1730_IDI, 2, 0, },
+ .sdo[0] = { 16, PCI1730_DO, 2, 0, },
+ .sdo[1] = { 16, PCI1730_IDO, 2, 0, },
+ .boardid = { 4, PCI173x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1733] = {
+ .name = "pci1733",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1733,
+ .nsubdevs = 2,
+ .sdi[1] = { 32, PCI1733_IDI, 4, 0, },
+ .boardid = { 4, PCI173x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1734] = {
+ .name = "pci1734",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1734,
+ .nsubdevs = 2,
+ .sdo[1] = { 32, PCI1734_IDO, 4, 0, },
+ .boardid = { 4, PCI173x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1735] = {
+ .name = "pci1735",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1735,
+ .nsubdevs = 4,
+ .sdi[0] = { 32, PCI1735_DI, 4, 0, },
+ .sdo[0] = { 32, PCI1735_DO, 4, 0, },
+ .boardid = { 4, PCI1735_BOARDID, 1, SDF_INTERNAL, },
+ .timer_regbase = PCI1735_C8254,
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1736] = {
+ .name = "pci1736",
+ .main_pci_region = PCI1736_MAINREG,
+ .cardtype = TYPE_PCI1736,
+ .nsubdevs = 3,
+ .sdi[1] = { 16, PCI1736_IDI, 2, 0, },
+ .sdo[1] = { 16, PCI1736_IDO, 2, 0, },
+ .boardid = { 4, PCI1736_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1739] = {
+ .name = "pci1739",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1739,
+ .nsubdevs = 2,
+ .sdio[0] = { 48, PCI1739_DIO, 2, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1750] = {
+ .name = "pci1750",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1750,
+ .nsubdevs = 2,
+ .sdi[1] = { 16, PCI1750_IDI, 2, 0, },
+ .sdo[1] = { 16, PCI1750_IDO, 2, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1751] = {
+ .name = "pci1751",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1751,
+ .nsubdevs = 3,
+ .sdio[0] = { 48, PCI1751_DIO, 2, 0, },
+ .timer_regbase = PCI1751_CNT,
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1752] = {
+ .name = "pci1752",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1752,
+ .nsubdevs = 3,
+ .sdo[0] = { 32, PCI1752_IDO, 2, 0, },
+ .sdo[1] = { 32, PCI1752_IDO2, 2, 0, },
+ .boardid = { 4, PCI175x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+ [TYPE_PCI1753] = {
+ .name = "pci1753",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1753,
+ .nsubdevs = 4,
+ .sdio[0] = { 96, PCI1753_DIO, 4, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1753E] = {
+ .name = "pci1753e",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1753E,
+ .nsubdevs = 8,
+ .sdio[0] = { 96, PCI1753_DIO, 4, 0, },
+ .sdio[1] = { 96, PCI1753E_DIO, 4, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1754] = {
+ .name = "pci1754",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1754,
+ .nsubdevs = 3,
+ .sdi[0] = { 32, PCI1754_IDI, 2, 0, },
+ .sdi[1] = { 32, PCI1754_IDI2, 2, 0, },
+ .boardid = { 4, PCI175x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+ [TYPE_PCI1756] = {
+ .name = "pci1756",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1756,
+ .nsubdevs = 3,
+ .sdi[1] = { 32, PCI1756_IDI, 2, 0, },
+ .sdo[1] = { 32, PCI1756_IDO, 2, 0, },
+ .boardid = { 4, PCI175x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+ [TYPE_PCI1760] = {
+ /* This card has its own 'attach' */
+ .name = "pci1760",
+ .main_pci_region = 0,
+ .cardtype = TYPE_PCI1760,
+ .nsubdevs = 4,
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1762] = {
+ .name = "pci1762",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1762,
+ .nsubdevs = 3,
+ .sdi[1] = { 16, PCI1762_IDI, 1, 0, },
+ .sdo[1] = { 16, PCI1762_RO, 1, 0, },
+ .boardid = { 4, PCI1762_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+};
+
+struct pci_dio_private {
+ char GlobalIrqEnabled; /* 1= any IRQ source is enabled */
+ /* PCI-1760 specific data */
+ unsigned char IDICntEnable; /* counter's counting enable status */
+ unsigned char IDICntOverEnable; /* counter's overflow interrupts enable
+ * status */
+ unsigned char IDICntMatchEnable; /* counter's match interrupts
+ * enable status */
+ unsigned char IDICntEdge; /* counter's count edge value
+ * (bit=0 - rising, =1 - falling) */
+ unsigned short CntResValue[8]; /* counters' reset value */
+ unsigned short CntMatchValue[8]; /* counters' match interrupt value */
+ unsigned char IDIFiltersEn; /* IDI's digital filters enable status */
+ unsigned char IDIPatMatchEn; /* IDI's pattern match enable status */
+ unsigned char IDIPatMatchValue; /* IDI's pattern match value */
+ unsigned short IDIFiltrLow[8]; /* IDI's filter value low signal */
+ unsigned short IDIFiltrHigh[8]; /* IDI's filter value high signal */
+};
+
+/*
+==============================================================================
+*/
+static int pci_dio_insn_bits_di_b(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct diosubd_data *d = (const struct diosubd_data *)s->private;
+ int i;
+
+ data[1] = 0;
+ for (i = 0; i < d->regs; i++)
+ data[1] |= inb(dev->iobase + d->addr + i) << (8 * i);
+
+ return insn->n;
+}
+
+/*
+==============================================================================
+*/
+static int pci_dio_insn_bits_di_w(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct diosubd_data *d = (const struct diosubd_data *)s->private;
+ int i;
+
+ data[1] = 0;
+ for (i = 0; i < d->regs; i++)
+ data[1] |= inw(dev->iobase + d->addr + 2 * i) << (16 * i);
+
+ return insn->n;
+}
+
+static int pci_dio_insn_bits_do_b(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct diosubd_data *d = (const struct diosubd_data *)s->private;
+ int i;
+
+ if (comedi_dio_update_state(s, data)) {
+ for (i = 0; i < d->regs; i++)
+ outb((s->state >> (8 * i)) & 0xff,
+ dev->iobase + d->addr + i);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci_dio_insn_bits_do_w(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct diosubd_data *d = (const struct diosubd_data *)s->private;
+ int i;
+
+ if (comedi_dio_update_state(s, data)) {
+ for (i = 0; i < d->regs; i++)
+ outw((s->state >> (16 * i)) & 0xffff,
+ dev->iobase + d->addr + 2 * i);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+/*
+==============================================================================
+*/
+static int pci1760_unchecked_mbxrequest(struct comedi_device *dev,
+ unsigned char *omb, unsigned char *imb,
+ int repeats)
+{
+ int cnt, tout, ok = 0;
+
+ for (cnt = 0; cnt < repeats; cnt++) {
+ outb(omb[0], dev->iobase + OMB0);
+ outb(omb[1], dev->iobase + OMB1);
+ outb(omb[2], dev->iobase + OMB2);
+ outb(omb[3], dev->iobase + OMB3);
+ for (tout = 0; tout < 251; tout++) {
+ imb[2] = inb(dev->iobase + IMB2);
+ if (imb[2] == omb[2]) {
+ imb[0] = inb(dev->iobase + IMB0);
+ imb[1] = inb(dev->iobase + IMB1);
+ imb[3] = inb(dev->iobase + IMB3);
+ ok = 1;
+ break;
+ }
+ udelay(1);
+ }
+ if (ok)
+ return 0;
+ }
+
+ dev_err(dev->class_dev, "PCI-1760 mailbox request timeout!\n");
+ return -ETIME;
+}
+
+static int pci1760_clear_imb2(struct comedi_device *dev)
+{
+ unsigned char omb[4] = { 0x0, 0x0, CMD_ClearIMB2, 0x0 };
+ unsigned char imb[4];
+ /* check if imb2 is already clear */
+ if (inb(dev->iobase + IMB2) == CMD_ClearIMB2)
+ return 0;
+ return pci1760_unchecked_mbxrequest(dev, omb, imb, OMBCMD_RETRY);
+}
+
+static int pci1760_mbxrequest(struct comedi_device *dev,
+ unsigned char *omb, unsigned char *imb)
+{
+ if (omb[2] == CMD_ClearIMB2) {
+ dev_err(dev->class_dev,
+ "bug! this function should not be used for CMD_ClearIMB2 command\n");
+ return -EINVAL;
+ }
+ if (inb(dev->iobase + IMB2) == omb[2]) {
+ int retval;
+
+ retval = pci1760_clear_imb2(dev);
+ if (retval < 0)
+ return retval;
+ }
+ return pci1760_unchecked_mbxrequest(dev, omb, imb, OMBCMD_RETRY);
+}
+
+/*
+==============================================================================
+*/
+static int pci1760_insn_bits_di(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = inb(dev->iobase + IMB3);
+
+ return insn->n;
+}
+
+static int pci1760_insn_bits_do(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ unsigned char omb[4] = {
+ 0x00,
+ 0x00,
+ CMD_SetRelaysOutput,
+ 0x00
+ };
+ unsigned char imb[4];
+
+ if (comedi_dio_update_state(s, data)) {
+ omb[0] = s->state;
+ ret = pci1760_mbxrequest(dev, omb, imb);
+ if (!ret)
+ return ret;
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+/*
+==============================================================================
+*/
+static int pci1760_insn_cnt_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int ret, n;
+ unsigned char omb[4] = {
+ CR_CHAN(insn->chanspec) & 0x07,
+ 0x00,
+ CMD_GetIDICntCurValue,
+ 0x00
+ };
+ unsigned char imb[4];
+
+ for (n = 0; n < insn->n; n++) {
+ ret = pci1760_mbxrequest(dev, omb, imb);
+ if (!ret)
+ return ret;
+ data[n] = (imb[1] << 8) + imb[0];
+ }
+
+ return n;
+}
+
+/*
+==============================================================================
+*/
+static int pci1760_insn_cnt_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct pci_dio_private *devpriv = dev->private;
+ int ret;
+ unsigned char chan = CR_CHAN(insn->chanspec) & 0x07;
+ unsigned char bitmask = 1 << chan;
+ unsigned char omb[4] = {
+ data[0] & 0xff,
+ (data[0] >> 8) & 0xff,
+ CMD_SetIDI0CntResetValue + chan,
+ 0x00
+ };
+ unsigned char imb[4];
+
+ /* Set reset value if different */
+ if (devpriv->CntResValue[chan] != (data[0] & 0xffff)) {
+ ret = pci1760_mbxrequest(dev, omb, imb);
+ if (!ret)
+ return ret;
+ devpriv->CntResValue[chan] = data[0] & 0xffff;
+ }
+
+ omb[0] = bitmask; /* reset counter to it reset value */
+ omb[2] = CMD_ResetIDICounters;
+ ret = pci1760_mbxrequest(dev, omb, imb);
+ if (!ret)
+ return ret;
+
+ /* start counter if it don't run */
+ if (!(bitmask & devpriv->IDICntEnable)) {
+ omb[0] = bitmask;
+ omb[2] = CMD_EnableIDICounters;
+ ret = pci1760_mbxrequest(dev, omb, imb);
+ if (!ret)
+ return ret;
+ devpriv->IDICntEnable |= bitmask;
+ }
+ return 1;
+}
+
+/*
+==============================================================================
+*/
+static int pci1760_reset(struct comedi_device *dev)
+{
+ struct pci_dio_private *devpriv = dev->private;
+ int i;
+ unsigned char omb[4] = { 0x00, 0x00, 0x00, 0x00 };
+ unsigned char imb[4];
+
+ outb(0, dev->iobase + INTCSR0); /* disable IRQ */
+ outb(0, dev->iobase + INTCSR1);
+ outb(0, dev->iobase + INTCSR2);
+ outb(0, dev->iobase + INTCSR3);
+ devpriv->GlobalIrqEnabled = 0;
+
+ omb[0] = 0x00;
+ omb[2] = CMD_SetRelaysOutput; /* reset relay outputs */
+ pci1760_mbxrequest(dev, omb, imb);
+
+ omb[0] = 0x00;
+ omb[2] = CMD_EnableIDICounters; /* disable IDI up counters */
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->IDICntEnable = 0;
+
+ omb[0] = 0x00;
+ omb[2] = CMD_OverflowIDICounters; /* disable counters overflow
+ * interrupts */
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->IDICntOverEnable = 0;
+
+ omb[0] = 0x00;
+ omb[2] = CMD_MatchIntIDICounters; /* disable counters match value
+ * interrupts */
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->IDICntMatchEnable = 0;
+
+ omb[0] = 0x00;
+ omb[1] = 0x80;
+ for (i = 0; i < 8; i++) { /* set IDI up counters match value */
+ omb[2] = CMD_SetIDI0CntMatchValue + i;
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->CntMatchValue[i] = 0x8000;
+ }
+
+ omb[0] = 0x00;
+ omb[1] = 0x00;
+ for (i = 0; i < 8; i++) { /* set IDI up counters reset value */
+ omb[2] = CMD_SetIDI0CntResetValue + i;
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->CntResValue[i] = 0x0000;
+ }
+
+ omb[0] = 0xff;
+ omb[2] = CMD_ResetIDICounters; /* reset IDI up counters to reset
+ * values */
+ pci1760_mbxrequest(dev, omb, imb);
+
+ omb[0] = 0x00;
+ omb[2] = CMD_EdgeIDICounters; /* set IDI up counters count edge */
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->IDICntEdge = 0x00;
+
+ omb[0] = 0x00;
+ omb[2] = CMD_EnableIDIFilters; /* disable all digital in filters */
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->IDIFiltersEn = 0x00;
+
+ omb[0] = 0x00;
+ omb[2] = CMD_EnableIDIPatternMatch; /* disable pattern matching */
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->IDIPatMatchEn = 0x00;
+
+ omb[0] = 0x00;
+ omb[2] = CMD_SetIDIPatternMatch; /* set pattern match value */
+ pci1760_mbxrequest(dev, omb, imb);
+ devpriv->IDIPatMatchValue = 0x00;
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci_dio_reset(struct comedi_device *dev)
+{
+ const struct dio_boardtype *this_board = dev->board_ptr;
+
+ switch (this_board->cardtype) {
+ case TYPE_PCI1730:
+ outb(0, dev->iobase + PCI1730_DO); /* clear outputs */
+ outb(0, dev->iobase + PCI1730_DO + 1);
+ outb(0, dev->iobase + PCI1730_IDO);
+ outb(0, dev->iobase + PCI1730_IDO + 1);
+ /* fallthrough */
+ case TYPE_PCI1733:
+ /* disable interrupts */
+ outb(0, dev->iobase + PCI1730_3_INT_EN);
+ /* clear interrupts */
+ outb(0x0f, dev->iobase + PCI1730_3_INT_CLR);
+ /* set rising edge trigger */
+ outb(0, dev->iobase + PCI1730_3_INT_RF);
+ break;
+ case TYPE_PCI1734:
+ outb(0, dev->iobase + PCI1734_IDO); /* clear outputs */
+ outb(0, dev->iobase + PCI1734_IDO + 1);
+ outb(0, dev->iobase + PCI1734_IDO + 2);
+ outb(0, dev->iobase + PCI1734_IDO + 3);
+ break;
+ case TYPE_PCI1735:
+ outb(0, dev->iobase + PCI1735_DO); /* clear outputs */
+ outb(0, dev->iobase + PCI1735_DO + 1);
+ outb(0, dev->iobase + PCI1735_DO + 2);
+ outb(0, dev->iobase + PCI1735_DO + 3);
+ break;
+
+ case TYPE_PCI1736:
+ outb(0, dev->iobase + PCI1736_IDO);
+ outb(0, dev->iobase + PCI1736_IDO + 1);
+ /* disable interrupts */
+ outb(0, dev->iobase + PCI1736_3_INT_EN);
+ /* clear interrupts */
+ outb(0x0f, dev->iobase + PCI1736_3_INT_CLR);
+ /* set rising edge trigger */
+ outb(0, dev->iobase + PCI1736_3_INT_RF);
+ break;
+
+ case TYPE_PCI1739:
+ /* disable & clear interrupts */
+ outb(0x88, dev->iobase + PCI1739_ICR);
+ break;
+
+ case TYPE_PCI1750:
+ case TYPE_PCI1751:
+ /* disable & clear interrupts */
+ outb(0x88, dev->iobase + PCI1750_ICR);
+ break;
+ case TYPE_PCI1752:
+ outw(0, dev->iobase + PCI1752_6_CFC); /* disable channel freeze
+ * function */
+ outw(0, dev->iobase + PCI1752_IDO); /* clear outputs */
+ outw(0, dev->iobase + PCI1752_IDO + 2);
+ outw(0, dev->iobase + PCI1752_IDO2);
+ outw(0, dev->iobase + PCI1752_IDO2 + 2);
+ break;
+ case TYPE_PCI1753E:
+ outb(0x88, dev->iobase + PCI1753E_ICR0); /* disable & clear
+ * interrupts */
+ outb(0x80, dev->iobase + PCI1753E_ICR1);
+ outb(0x80, dev->iobase + PCI1753E_ICR2);
+ outb(0x80, dev->iobase + PCI1753E_ICR3);
+ /* fallthrough */
+ case TYPE_PCI1753:
+ outb(0x88, dev->iobase + PCI1753_ICR0); /* disable & clear
+ * interrupts */
+ outb(0x80, dev->iobase + PCI1753_ICR1);
+ outb(0x80, dev->iobase + PCI1753_ICR2);
+ outb(0x80, dev->iobase + PCI1753_ICR3);
+ break;
+ case TYPE_PCI1754:
+ outw(0x08, dev->iobase + PCI1754_6_ICR0); /* disable and clear
+ * interrupts */
+ outw(0x08, dev->iobase + PCI1754_6_ICR1);
+ outw(0x08, dev->iobase + PCI1754_ICR2);
+ outw(0x08, dev->iobase + PCI1754_ICR3);
+ break;
+ case TYPE_PCI1756:
+ outw(0, dev->iobase + PCI1752_6_CFC); /* disable channel freeze
+ * function */
+ outw(0x08, dev->iobase + PCI1754_6_ICR0); /* disable and clear
+ * interrupts */
+ outw(0x08, dev->iobase + PCI1754_6_ICR1);
+ outw(0, dev->iobase + PCI1756_IDO); /* clear outputs */
+ outw(0, dev->iobase + PCI1756_IDO + 2);
+ break;
+ case TYPE_PCI1760:
+ pci1760_reset(dev);
+ break;
+ case TYPE_PCI1762:
+ outw(0x0101, dev->iobase + PCI1762_ICR); /* disable & clear
+ * interrupts */
+ break;
+ }
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci1760_attach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->len_chanlist = 8;
+ s->range_table = &range_digital;
+ s->insn_bits = pci1760_insn_bits_di;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->len_chanlist = 8;
+ s->range_table = &range_digital;
+ s->state = 0;
+ s->insn_bits = pci1760_insn_bits_do;
+
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITABLE | SDF_LSAMPL;
+ s->n_chan = 2;
+ s->maxdata = 0xffffffff;
+ s->len_chanlist = 2;
+/* s->insn_config=pci1760_insn_pwm_cfg; */
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 8;
+ s->insn_read = pci1760_insn_cnt_read;
+ s->insn_write = pci1760_insn_cnt_write;
+/* s->insn_config=pci1760_insn_cnt_cfg; */
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci_dio_add_di(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ const struct diosubd_data *d)
+{
+ const struct dio_boardtype *this_board = dev->board_ptr;
+
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | d->specflags;
+ if (d->chans > 16)
+ s->subdev_flags |= SDF_LSAMPL;
+ s->n_chan = d->chans;
+ s->maxdata = 1;
+ s->len_chanlist = d->chans;
+ s->range_table = &range_digital;
+ switch (this_board->io_access) {
+ case IO_8b:
+ s->insn_bits = pci_dio_insn_bits_di_b;
+ break;
+ case IO_16b:
+ s->insn_bits = pci_dio_insn_bits_di_w;
+ break;
+ }
+ s->private = (void *)d;
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci_dio_add_do(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ const struct diosubd_data *d)
+{
+ const struct dio_boardtype *this_board = dev->board_ptr;
+
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ if (d->chans > 16)
+ s->subdev_flags |= SDF_LSAMPL;
+ s->n_chan = d->chans;
+ s->maxdata = 1;
+ s->len_chanlist = d->chans;
+ s->range_table = &range_digital;
+ s->state = 0;
+ switch (this_board->io_access) {
+ case IO_8b:
+ s->insn_bits = pci_dio_insn_bits_do_b;
+ break;
+ case IO_16b:
+ s->insn_bits = pci_dio_insn_bits_do_w;
+ break;
+ }
+ s->private = (void *)d;
+
+ return 0;
+}
+
+static unsigned long pci_dio_override_cardtype(struct pci_dev *pcidev,
+ unsigned long cardtype)
+{
+ /*
+ * Change cardtype from TYPE_PCI1753 to TYPE_PCI1753E if expansion
+ * board available. Need to enable PCI device and request the main
+ * registers PCI BAR temporarily to perform the test.
+ */
+ if (cardtype != TYPE_PCI1753)
+ return cardtype;
+ if (pci_enable_device(pcidev) < 0)
+ return cardtype;
+ if (pci_request_region(pcidev, PCIDIO_MAINREG, "adv_pci_dio") == 0) {
+ /*
+ * This test is based on Advantech's "advdaq" driver source
+ * (which declares its module licence as "GPL" although the
+ * driver source does not include a "COPYING" file).
+ */
+ unsigned long reg =
+ pci_resource_start(pcidev, PCIDIO_MAINREG) + 53;
+
+ outb(0x05, reg);
+ if ((inb(reg) & 0x07) == 0x02) {
+ outb(0x02, reg);
+ if ((inb(reg) & 0x07) == 0x05)
+ cardtype = TYPE_PCI1753E;
+ }
+ pci_release_region(pcidev, PCIDIO_MAINREG);
+ }
+ pci_disable_device(pcidev);
+ return cardtype;
+}
+
+static int pci_dio_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct dio_boardtype *this_board = NULL;
+ struct pci_dio_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret, subdev, i, j;
+
+ if (context < ARRAY_SIZE(boardtypes))
+ this_board = &boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, this_board->main_pci_region);
+
+ ret = comedi_alloc_subdevices(dev, this_board->nsubdevs);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+ for (i = 0; i < MAX_DI_SUBDEVS; i++)
+ if (this_board->sdi[i].chans) {
+ s = &dev->subdevices[subdev];
+ pci_dio_add_di(dev, s, &this_board->sdi[i]);
+ subdev++;
+ }
+
+ for (i = 0; i < MAX_DO_SUBDEVS; i++)
+ if (this_board->sdo[i].chans) {
+ s = &dev->subdevices[subdev];
+ pci_dio_add_do(dev, s, &this_board->sdo[i]);
+ subdev++;
+ }
+
+ for (i = 0; i < MAX_DIO_SUBDEVG; i++)
+ for (j = 0; j < this_board->sdio[i].regs; j++) {
+ s = &dev->subdevices[subdev];
+ ret = subdev_8255_init(dev, s, NULL,
+ this_board->sdio[i].addr +
+ j * I8255_SIZE);
+ if (ret)
+ return ret;
+ subdev++;
+ }
+
+ if (this_board->boardid.chans) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DI;
+ pci_dio_add_di(dev, s, &this_board->boardid);
+ subdev++;
+ }
+
+ if (this_board->timer_regbase) {
+ s = &dev->subdevices[subdev];
+
+ dev->pacer = comedi_8254_init(dev->iobase +
+ this_board->timer_regbase,
+ 0, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ comedi_8254_subdevice_init(s, dev->pacer);
+
+ subdev++;
+ }
+
+ if (this_board->cardtype == TYPE_PCI1760)
+ pci1760_attach(dev);
+
+ pci_dio_reset(dev);
+
+ return 0;
+}
+
+static void pci_dio_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ pci_dio_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver adv_pci_dio_driver = {
+ .driver_name = "adv_pci_dio",
+ .module = THIS_MODULE,
+ .auto_attach = pci_dio_auto_attach,
+ .detach = pci_dio_detach,
+};
+
+static int adv_pci_dio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ unsigned long cardtype;
+
+ cardtype = pci_dio_override_cardtype(dev, id->driver_data);
+ return comedi_pci_auto_config(dev, &adv_pci_dio_driver, cardtype);
+}
+
+static const struct pci_device_id adv_pci_dio_pci_table[] = {
+ { PCI_VDEVICE(ADVANTECH, 0x1730), TYPE_PCI1730 },
+ { PCI_VDEVICE(ADVANTECH, 0x1733), TYPE_PCI1733 },
+ { PCI_VDEVICE(ADVANTECH, 0x1734), TYPE_PCI1734 },
+ { PCI_VDEVICE(ADVANTECH, 0x1735), TYPE_PCI1735 },
+ { PCI_VDEVICE(ADVANTECH, 0x1736), TYPE_PCI1736 },
+ { PCI_VDEVICE(ADVANTECH, 0x1739), TYPE_PCI1739 },
+ { PCI_VDEVICE(ADVANTECH, 0x1750), TYPE_PCI1750 },
+ { PCI_VDEVICE(ADVANTECH, 0x1751), TYPE_PCI1751 },
+ { PCI_VDEVICE(ADVANTECH, 0x1752), TYPE_PCI1752 },
+ { PCI_VDEVICE(ADVANTECH, 0x1753), TYPE_PCI1753 },
+ { PCI_VDEVICE(ADVANTECH, 0x1754), TYPE_PCI1754 },
+ { PCI_VDEVICE(ADVANTECH, 0x1756), TYPE_PCI1756 },
+ { PCI_VDEVICE(ADVANTECH, 0x1760), TYPE_PCI1760 },
+ { PCI_VDEVICE(ADVANTECH, 0x1762), TYPE_PCI1762 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci_dio_pci_table);
+
+static struct pci_driver adv_pci_dio_pci_driver = {
+ .name = "adv_pci_dio",
+ .id_table = adv_pci_dio_pci_table,
+ .probe = adv_pci_dio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci_dio_driver, adv_pci_dio_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/aio_aio12_8.c b/drivers/staging/comedi/drivers/aio_aio12_8.c
new file mode 100644
index 000000000..fbc3e5aa9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/aio_aio12_8.c
@@ -0,0 +1,249 @@
+/*
+
+ comedi/drivers/aio_aio12_8.c
+
+ Driver for Access I/O Products PC-104 AIO12-8 Analog I/O Board
+ Copyright (C) 2006 C&C Technologies, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+
+Driver: aio_aio12_8
+Description: Access I/O Products PC-104 AIO12-8 Analog I/O Board
+Author: Pablo Mejia <pablo.mejia@cctechnol.com>
+Devices: [Access I/O] PC-104 AIO12-8 (aio_aio12_8)
+ [Access I/O] PC-104 AI12-8 (aio_ai12_8)
+ [Access I/O] PC-104 AO12-8 (aio_ao12_8)
+Status: experimental
+
+Configuration Options:
+ [0] - I/O port base address
+
+Notes:
+
+ Only synchronous operations are supported.
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+#include "8255.h"
+
+/*
+ * Register map
+ */
+#define AIO12_8_STATUS_REG 0x00
+#define AIO12_8_STATUS_ADC_EOC (1 << 7)
+#define AIO12_8_STATUS_PORT_C_COS (1 << 6)
+#define AIO12_8_STATUS_IRQ_ENA (1 << 2)
+#define AIO12_8_INTERRUPT_REG 0x01
+#define AIO12_8_INTERRUPT_ADC (1 << 7)
+#define AIO12_8_INTERRUPT_COS (1 << 6)
+#define AIO12_8_INTERRUPT_COUNTER1 (1 << 5)
+#define AIO12_8_INTERRUPT_PORT_C3 (1 << 4)
+#define AIO12_8_INTERRUPT_PORT_C0 (1 << 3)
+#define AIO12_8_INTERRUPT_ENA (1 << 2)
+#define AIO12_8_ADC_REG 0x02
+#define AIO12_8_ADC_MODE_NORMAL (0 << 6)
+#define AIO12_8_ADC_MODE_INT_CLK (1 << 6)
+#define AIO12_8_ADC_MODE_STANDBY (2 << 6)
+#define AIO12_8_ADC_MODE_POWERDOWN (3 << 6)
+#define AIO12_8_ADC_ACQ_3USEC (0 << 5)
+#define AIO12_8_ADC_ACQ_PROGRAM (1 << 5)
+#define AIO12_8_ADC_RANGE(x) ((x) << 3)
+#define AIO12_8_ADC_CHAN(x) ((x) << 0)
+#define AIO12_8_DAC_REG(x) (0x04 + (x) * 2)
+#define AIO12_8_8254_BASE_REG 0x0c
+#define AIO12_8_8255_BASE_REG 0x10
+#define AIO12_8_DIO_CONTROL_REG 0x14
+#define AIO12_8_DIO_CONTROL_TST (1 << 0)
+#define AIO12_8_ADC_TRIGGER_REG 0x15
+#define AIO12_8_ADC_TRIGGER_RANGE(x) ((x) << 3)
+#define AIO12_8_ADC_TRIGGER_CHAN(x) ((x) << 0)
+#define AIO12_8_TRIGGER_REG 0x16
+#define AIO12_8_TRIGGER_ADTRIG (1 << 1)
+#define AIO12_8_TRIGGER_DACTRIG (1 << 0)
+#define AIO12_8_COS_REG 0x17
+#define AIO12_8_DAC_ENABLE_REG 0x18
+#define AIO12_8_DAC_ENABLE_REF_ENA (1 << 0)
+
+struct aio12_8_boardtype {
+ const char *name;
+ int ai_nchan;
+ int ao_nchan;
+};
+
+static const struct aio12_8_boardtype board_types[] = {
+ {
+ .name = "aio_aio12_8",
+ .ai_nchan = 8,
+ .ao_nchan = 4,
+ }, {
+ .name = "aio_ai12_8",
+ .ai_nchan = 8,
+ }, {
+ .name = "aio_ao12_8",
+ .ao_nchan = 4,
+ },
+};
+
+static int aio_aio12_8_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + AIO12_8_STATUS_REG);
+ if (status & AIO12_8_STATUS_ADC_EOC)
+ return 0;
+ return -EBUSY;
+}
+
+static int aio_aio12_8_ai_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned char control;
+ int ret;
+ int n;
+
+ /*
+ * Setup the control byte for internal 2MHz clock, 3uS conversion,
+ * at the desired range of the requested channel.
+ */
+ control = AIO12_8_ADC_MODE_NORMAL | AIO12_8_ADC_ACQ_3USEC |
+ AIO12_8_ADC_RANGE(range) | AIO12_8_ADC_CHAN(chan);
+
+ /* Read status to clear EOC latch */
+ inb(dev->iobase + AIO12_8_STATUS_REG);
+
+ for (n = 0; n < insn->n; n++) {
+ /* Setup and start conversion */
+ outb(control, dev->iobase + AIO12_8_ADC_REG);
+
+ /* Wait for conversion to complete */
+ ret = comedi_timeout(dev, s, insn, aio_aio12_8_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[n] = inw(dev->iobase + AIO12_8_ADC_REG) & s->maxdata;
+ }
+
+ return insn->n;
+}
+
+static int aio_aio12_8_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ /* enable DACs */
+ outb(AIO12_8_DAC_ENABLE_REF_ENA, dev->iobase + AIO12_8_DAC_ENABLE_REG);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, dev->iobase + AIO12_8_DAC_REG(chan));
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static const struct comedi_lrange range_aio_aio12_8 = {
+ 4, {
+ UNI_RANGE(5),
+ BIP_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(10)
+ }
+};
+
+static int aio_aio12_8_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct aio12_8_boardtype *board = dev->board_ptr;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 32);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ if (board->ai_nchan) {
+ /* Analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = board->ai_nchan;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_aio_aio12_8;
+ s->insn_read = aio_aio12_8_ai_read;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[1];
+ if (board->ao_nchan) {
+ /* Analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_aio_aio12_8;
+ s->insn_write = aio_aio12_8_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* 8255 Digital i/o subdevice */
+ ret = subdev_8255_init(dev, s, NULL, AIO12_8_8255_BASE_REG);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[3];
+ /* 8254 counter/timer subdevice */
+ s->type = COMEDI_SUBD_UNUSED;
+
+ return 0;
+}
+
+static struct comedi_driver aio_aio12_8_driver = {
+ .driver_name = "aio_aio12_8",
+ .module = THIS_MODULE,
+ .attach = aio_aio12_8_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &board_types[0].name,
+ .num_names = ARRAY_SIZE(board_types),
+ .offset = sizeof(struct aio12_8_boardtype),
+};
+module_comedi_driver(aio_aio12_8_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/aio_iiro_16.c b/drivers/staging/comedi/drivers/aio_iiro_16.c
new file mode 100644
index 000000000..35b2f98f0
--- /dev/null
+++ b/drivers/staging/comedi/drivers/aio_iiro_16.c
@@ -0,0 +1,244 @@
+/*
+ * aio_iiro_16.c
+ * Comedi driver for Access I/O Products 104-IIRO-16 board
+ * Copyright (C) 2006 C&C Technologies, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: aio_iiro_16
+ * Description: Access I/O Products PC/104 Isolated Input/Relay Output Board
+ * Author: Zachary Ware <zach.ware@cctechnol.com>
+ * Devices: [Access I/O] 104-IIRO-16 (aio_iiro_16)
+ * Status: experimental
+ *
+ * Configuration Options:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional)
+ *
+ * The board supports interrupts on change of state of the digital inputs.
+ * The sample data returned by the async command indicates which inputs
+ * changed state and the current state of the inputs:
+ *
+ * Bit 23 - IRQ Enable (1) / Disable (0)
+ * Bit 17 - Input 8-15 Changed State (1 = Changed, 0 = No Change)
+ * Bit 16 - Input 0-7 Changed State (1 = Changed, 0 = No Change)
+ * Bit 15 - Digital input 15
+ * ...
+ * Bit 0 - Digital input 0
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#define AIO_IIRO_16_RELAY_0_7 0x00
+#define AIO_IIRO_16_INPUT_0_7 0x01
+#define AIO_IIRO_16_IRQ 0x02
+#define AIO_IIRO_16_RELAY_8_15 0x04
+#define AIO_IIRO_16_INPUT_8_15 0x05
+#define AIO_IIRO_16_STATUS 0x07
+#define AIO_IIRO_16_STATUS_IRQE BIT(7)
+#define AIO_IIRO_16_STATUS_INPUT_8_15 BIT(1)
+#define AIO_IIRO_16_STATUS_INPUT_0_7 BIT(0)
+
+static unsigned int aio_iiro_16_read_inputs(struct comedi_device *dev)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + AIO_IIRO_16_INPUT_0_7);
+ val |= inb(dev->iobase + AIO_IIRO_16_INPUT_8_15) << 8;
+
+ return val;
+}
+
+static irqreturn_t aio_iiro_16_cos(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+ unsigned int val;
+
+ status = inb(dev->iobase + AIO_IIRO_16_STATUS);
+ if (!(status & AIO_IIRO_16_STATUS_IRQE))
+ return IRQ_NONE;
+
+ val = aio_iiro_16_read_inputs(dev);
+ val |= (status << 16);
+
+ comedi_buf_write_samples(s, &val, 1);
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static void aio_iiro_enable_irq(struct comedi_device *dev, bool enable)
+{
+ if (enable)
+ inb(dev->iobase + AIO_IIRO_16_IRQ);
+ else
+ outb(0, dev->iobase + AIO_IIRO_16_IRQ);
+}
+
+static int aio_iiro_16_cos_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ aio_iiro_enable_irq(dev, false);
+
+ return 0;
+}
+
+static int aio_iiro_16_cos_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ aio_iiro_enable_irq(dev, true);
+
+ return 0;
+}
+
+static int aio_iiro_16_cos_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int aio_iiro_16_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase + AIO_IIRO_16_RELAY_0_7);
+ outb((s->state >> 8) & 0xff,
+ dev->iobase + AIO_IIRO_16_RELAY_8_15);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int aio_iiro_16_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = aio_iiro_16_read_inputs(dev);
+
+ return insn->n;
+}
+
+static int aio_iiro_16_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x8);
+ if (ret)
+ return ret;
+
+ aio_iiro_enable_irq(dev, false);
+
+ /*
+ * Digital input change of state interrupts are optionally supported
+ * using IRQ 2-7, 10-12, 14, or 15.
+ */
+ if ((1 << it->options[1]) & 0xdcfc) {
+ ret = request_irq(it->options[1], aio_iiro_16_cos, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = aio_iiro_16_do_insn_bits;
+
+ /* get the initial state of the relays */
+ s->state = inb(dev->iobase + AIO_IIRO_16_RELAY_0_7) |
+ (inb(dev->iobase + AIO_IIRO_16_RELAY_8_15) << 8);
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = aio_iiro_16_di_insn_bits;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL;
+ s->len_chanlist = 1;
+ s->do_cmdtest = aio_iiro_16_cos_cmdtest;
+ s->do_cmd = aio_iiro_16_cos_cmd;
+ s->cancel = aio_iiro_16_cos_cancel;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver aio_iiro_16_driver = {
+ .driver_name = "aio_iiro_16",
+ .module = THIS_MODULE,
+ .attach = aio_iiro_16_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(aio_iiro_16_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Access I/O Products 104-IIRO-16 board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amcc_s5933.h b/drivers/staging/comedi/drivers/amcc_s5933.h
new file mode 100644
index 000000000..d4b8c0195
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amcc_s5933.h
@@ -0,0 +1,176 @@
+/*
+ comedi/drivers/amcc_s5933.h
+
+ Stuff for AMCC S5933 PCI Controller
+
+ Author: Michal Dobes <dobes@tesnet.cz>
+
+ Inspirated from general-purpose AMCC S5933 PCI Matchmaker driver
+ made by Andrea Cisternino <acister@pcape1.pi.infn.it>
+ and as result of espionage from MITE code made by David A. Schleef.
+ Thanks to AMCC for their on-line documentation and bus master DMA
+ example.
+*/
+
+#ifndef _AMCC_S5933_H_
+#define _AMCC_S5933_H_
+
+/****************************************************************************/
+/* AMCC Operation Register Offsets - PCI */
+/****************************************************************************/
+
+#define AMCC_OP_REG_OMB1 0x00
+#define AMCC_OP_REG_OMB2 0x04
+#define AMCC_OP_REG_OMB3 0x08
+#define AMCC_OP_REG_OMB4 0x0c
+#define AMCC_OP_REG_IMB1 0x10
+#define AMCC_OP_REG_IMB2 0x14
+#define AMCC_OP_REG_IMB3 0x18
+#define AMCC_OP_REG_IMB4 0x1c
+#define AMCC_OP_REG_FIFO 0x20
+#define AMCC_OP_REG_MWAR 0x24
+#define AMCC_OP_REG_MWTC 0x28
+#define AMCC_OP_REG_MRAR 0x2c
+#define AMCC_OP_REG_MRTC 0x30
+#define AMCC_OP_REG_MBEF 0x34
+#define AMCC_OP_REG_INTCSR 0x38
+#define AMCC_OP_REG_INTCSR_SRC (AMCC_OP_REG_INTCSR + 2) /* INT source */
+#define AMCC_OP_REG_INTCSR_FEC (AMCC_OP_REG_INTCSR + 3) /* FIFO ctrl */
+#define AMCC_OP_REG_MCSR 0x3c
+#define AMCC_OP_REG_MCSR_NVDATA (AMCC_OP_REG_MCSR + 2) /* Data in byte 2 */
+#define AMCC_OP_REG_MCSR_NVCMD (AMCC_OP_REG_MCSR + 3) /* Command in byte 3 */
+
+#define AMCC_FIFO_DEPTH_DWORD 8
+#define AMCC_FIFO_DEPTH_BYTES (8 * sizeof(u32))
+
+/****************************************************************************/
+/* AMCC - PCI Interrupt Control/Status Register */
+/****************************************************************************/
+#define INTCSR_OUTBOX_BYTE(x) ((x) & 0x3)
+#define INTCSR_OUTBOX_SELECT(x) (((x) & 0x3) << 2)
+#define INTCSR_OUTBOX_EMPTY_INT 0x10 /* enable outbox empty interrupt */
+#define INTCSR_INBOX_BYTE(x) (((x) & 0x3) << 8)
+#define INTCSR_INBOX_SELECT(x) (((x) & 0x3) << 10)
+#define INTCSR_INBOX_FULL_INT 0x1000 /* enable inbox full interrupt */
+/* read, or write clear inbox full interrupt */
+#define INTCSR_INBOX_INTR_STATUS 0x20000
+/* read only, interrupt asserted */
+#define INTCSR_INTR_ASSERTED 0x800000
+
+/****************************************************************************/
+/* AMCC - PCI non-volatile ram command register (byte 3 of master control/status register) */
+/****************************************************************************/
+#define MCSR_NV_LOAD_LOW_ADDR 0x0
+#define MCSR_NV_LOAD_HIGH_ADDR 0x20
+#define MCSR_NV_WRITE 0x40
+#define MCSR_NV_READ 0x60
+#define MCSR_NV_MASK 0x60
+#define MCSR_NV_ENABLE 0x80
+#define MCSR_NV_BUSY MCSR_NV_ENABLE
+
+/****************************************************************************/
+/* AMCC Operation Registers Size - PCI */
+/****************************************************************************/
+
+#define AMCC_OP_REG_SIZE 64 /* in bytes */
+
+/****************************************************************************/
+/* AMCC Operation Register Offsets - Add-on */
+/****************************************************************************/
+
+#define AMCC_OP_REG_AIMB1 0x00
+#define AMCC_OP_REG_AIMB2 0x04
+#define AMCC_OP_REG_AIMB3 0x08
+#define AMCC_OP_REG_AIMB4 0x0c
+#define AMCC_OP_REG_AOMB1 0x10
+#define AMCC_OP_REG_AOMB2 0x14
+#define AMCC_OP_REG_AOMB3 0x18
+#define AMCC_OP_REG_AOMB4 0x1c
+#define AMCC_OP_REG_AFIFO 0x20
+#define AMCC_OP_REG_AMWAR 0x24
+#define AMCC_OP_REG_APTA 0x28
+#define AMCC_OP_REG_APTD 0x2c
+#define AMCC_OP_REG_AMRAR 0x30
+#define AMCC_OP_REG_AMBEF 0x34
+#define AMCC_OP_REG_AINT 0x38
+#define AMCC_OP_REG_AGCSTS 0x3c
+#define AMCC_OP_REG_AMWTC 0x58
+#define AMCC_OP_REG_AMRTC 0x5c
+
+/****************************************************************************/
+/* AMCC - Add-on General Control/Status Register */
+/****************************************************************************/
+
+#define AGCSTS_CONTROL_MASK 0xfffff000
+#define AGCSTS_NV_ACC_MASK 0xe0000000
+#define AGCSTS_RESET_MASK 0x0e000000
+#define AGCSTS_NV_DA_MASK 0x00ff0000
+#define AGCSTS_BIST_MASK 0x0000f000
+#define AGCSTS_STATUS_MASK 0x000000ff
+#define AGCSTS_TCZERO_MASK 0x000000c0
+#define AGCSTS_FIFO_ST_MASK 0x0000003f
+
+#define AGCSTS_TC_ENABLE 0x10000000
+
+#define AGCSTS_RESET_MBFLAGS 0x08000000
+#define AGCSTS_RESET_P2A_FIFO 0x04000000
+#define AGCSTS_RESET_A2P_FIFO 0x02000000
+#define AGCSTS_RESET_FIFOS (AGCSTS_RESET_A2P_FIFO | AGCSTS_RESET_P2A_FIFO)
+
+#define AGCSTS_A2P_TCOUNT 0x00000080
+#define AGCSTS_P2A_TCOUNT 0x00000040
+
+#define AGCSTS_FS_P2A_EMPTY 0x00000020
+#define AGCSTS_FS_P2A_HALF 0x00000010
+#define AGCSTS_FS_P2A_FULL 0x00000008
+
+#define AGCSTS_FS_A2P_EMPTY 0x00000004
+#define AGCSTS_FS_A2P_HALF 0x00000002
+#define AGCSTS_FS_A2P_FULL 0x00000001
+
+/****************************************************************************/
+/* AMCC - Add-on Interrupt Control/Status Register */
+/****************************************************************************/
+
+#define AINT_INT_MASK 0x00ff0000
+#define AINT_SEL_MASK 0x0000ffff
+#define AINT_IS_ENSEL_MASK 0x00001f1f
+
+#define AINT_INT_ASSERTED 0x00800000
+#define AINT_BM_ERROR 0x00200000
+#define AINT_BIST_INT 0x00100000
+
+#define AINT_RT_COMPLETE 0x00080000
+#define AINT_WT_COMPLETE 0x00040000
+
+#define AINT_OUT_MB_INT 0x00020000
+#define AINT_IN_MB_INT 0x00010000
+
+#define AINT_READ_COMPL 0x00008000
+#define AINT_WRITE_COMPL 0x00004000
+
+#define AINT_OMB_ENABLE 0x00001000
+#define AINT_OMB_SELECT 0x00000c00
+#define AINT_OMB_BYTE 0x00000300
+
+#define AINT_IMB_ENABLE 0x00000010
+#define AINT_IMB_SELECT 0x0000000c
+#define AINT_IMB_BYTE 0x00000003
+
+/* these are bits from various different registers, needs cleanup XXX */
+/* Enable Bus Mastering */
+#define EN_A2P_TRANSFERS 0x00000400
+/* FIFO Flag Reset */
+#define RESET_A2P_FLAGS 0x04000000L
+/* FIFO Relative Priority */
+#define A2P_HI_PRIORITY 0x00000100L
+/* Identify Interrupt Sources */
+#define ANY_S593X_INT 0x00800000L
+#define READ_TC_INT 0x00080000L
+#define WRITE_TC_INT 0x00040000L
+#define IN_MB_INT 0x00020000L
+#define MASTER_ABORT_INT 0x00100000L
+#define TARGET_ABORT_INT 0x00200000L
+#define BUS_MASTER_INT 0x00200000L
+
+#endif
diff --git a/drivers/staging/comedi/drivers/amplc_dio200.c b/drivers/staging/comedi/drivers/amplc_dio200.c
new file mode 100644
index 000000000..4fe118380
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200.c
@@ -0,0 +1,273 @@
+/*
+ comedi/drivers/amplc_dio200.c
+
+ Driver for Amplicon PC212E, PC214E, PC215E, PC218E, PC272E.
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+ * Driver: amplc_dio200
+ * Description: Amplicon 200 Series ISA Digital I/O
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Amplicon] PC212E (pc212e), PC214E (pc214e), PC215E (pc215e),
+ * PC218E (pc218e), PC272E (pc272e)
+ * Updated: Mon, 18 Mar 2013 14:40:41 +0000
+ *
+ * Status: works
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional, but commands won't work without it)
+ *
+ * Passing a zero for an option is the same as leaving it unspecified.
+ *
+ * SUBDEVICES
+ *
+ * PC212E PC214E PC215E
+ * ------------- ------------- -------------
+ * Subdevices 6 4 5
+ * 0 PPI-X PPI-X PPI-X
+ * 1 CTR-Y1 PPI-Y PPI-Y
+ * 2 CTR-Y2 CTR-Z1* CTR-Z1
+ * 3 CTR-Z1 INTERRUPT* CTR-Z2
+ * 4 CTR-Z2 INTERRUPT
+ * 5 INTERRUPT
+ *
+ * PC218E PC272E
+ * ------------- -------------
+ * Subdevices 7 4
+ * 0 CTR-X1 PPI-X
+ * 1 CTR-X2 PPI-Y
+ * 2 CTR-Y1 PPI-Z
+ * 3 CTR-Y2 INTERRUPT
+ * 4 CTR-Z1
+ * 5 CTR-Z2
+ * 6 INTERRUPT
+ *
+ * Each PPI is a 8255 chip providing 24 DIO channels. The DIO channels
+ * are configurable as inputs or outputs in four groups:
+ *
+ * Port A - channels 0 to 7
+ * Port B - channels 8 to 15
+ * Port CL - channels 16 to 19
+ * Port CH - channels 20 to 23
+ *
+ * Only mode 0 of the 8255 chips is supported.
+ *
+ * Each CTR is a 8254 chip providing 3 16-bit counter channels. Each
+ * channel is configured individually with INSN_CONFIG instructions. The
+ * specific type of configuration instruction is specified in data[0].
+ * Some configuration instructions expect an additional parameter in
+ * data[1]; others return a value in data[1]. The following configuration
+ * instructions are supported:
+ *
+ * INSN_CONFIG_SET_COUNTER_MODE. Sets the counter channel's mode and
+ * BCD/binary setting specified in data[1].
+ *
+ * INSN_CONFIG_8254_READ_STATUS. Reads the status register value for the
+ * counter channel into data[1].
+ *
+ * INSN_CONFIG_SET_CLOCK_SRC. Sets the counter channel's clock source as
+ * specified in data[1] (this is a hardware-specific value). Not
+ * supported on PC214E. For the other boards, valid clock sources are
+ * 0 to 7 as follows:
+ *
+ * 0. CLK n, the counter channel's dedicated CLK input from the SK1
+ * connector. (N.B. for other values, the counter channel's CLKn
+ * pin on the SK1 connector is an output!)
+ * 1. Internal 10 MHz clock.
+ * 2. Internal 1 MHz clock.
+ * 3. Internal 100 kHz clock.
+ * 4. Internal 10 kHz clock.
+ * 5. Internal 1 kHz clock.
+ * 6. OUT n-1, the output of counter channel n-1 (see note 1 below).
+ * 7. Ext Clock, the counter chip's dedicated Ext Clock input from
+ * the SK1 connector. This pin is shared by all three counter
+ * channels on the chip.
+ *
+ * INSN_CONFIG_GET_CLOCK_SRC. Returns the counter channel's current
+ * clock source in data[1]. For internal clock sources, data[2] is set
+ * to the period in ns.
+ *
+ * INSN_CONFIG_SET_GATE_SRC. Sets the counter channel's gate source as
+ * specified in data[2] (this is a hardware-specific value). Not
+ * supported on PC214E. For the other boards, valid gate sources are 0
+ * to 7 as follows:
+ *
+ * 0. VCC (internal +5V d.c.), i.e. gate permanently enabled.
+ * 1. GND (internal 0V d.c.), i.e. gate permanently disabled.
+ * 2. GAT n, the counter channel's dedicated GAT input from the SK1
+ * connector. (N.B. for other values, the counter channel's GATn
+ * pin on the SK1 connector is an output!)
+ * 3. /OUT n-2, the inverted output of counter channel n-2 (see note
+ * 2 below).
+ * 4. Reserved.
+ * 5. Reserved.
+ * 6. Reserved.
+ * 7. Reserved.
+ *
+ * INSN_CONFIG_GET_GATE_SRC. Returns the counter channel's current gate
+ * source in data[2].
+ *
+ * Clock and gate interconnection notes:
+ *
+ * 1. Clock source OUT n-1 is the output of the preceding channel on the
+ * same counter subdevice if n > 0, or the output of channel 2 on the
+ * preceding counter subdevice (see note 3) if n = 0.
+ *
+ * 2. Gate source /OUT n-2 is the inverted output of channel 0 on the
+ * same counter subdevice if n = 2, or the inverted output of channel n+1
+ * on the preceding counter subdevice (see note 3) if n < 2.
+ *
+ * 3. The counter subdevices are connected in a ring, so the highest
+ * counter subdevice precedes the lowest.
+ *
+ * The 'INTERRUPT' subdevice pretends to be a digital input subdevice. The
+ * digital inputs come from the interrupt status register. The number of
+ * channels matches the number of interrupt sources. The PC214E does not
+ * have an interrupt status register; see notes on 'INTERRUPT SOURCES'
+ * below.
+ *
+ * INTERRUPT SOURCES
+ *
+ * PC212E PC214E PC215E
+ * ------------- ------------- -------------
+ * Sources 6 1 6
+ * 0 PPI-X-C0 JUMPER-J5 PPI-X-C0
+ * 1 PPI-X-C3 PPI-X-C3
+ * 2 CTR-Y1-OUT1 PPI-Y-C0
+ * 3 CTR-Y2-OUT1 PPI-Y-C3
+ * 4 CTR-Z1-OUT1 CTR-Z1-OUT1
+ * 5 CTR-Z2-OUT1 CTR-Z2-OUT1
+ *
+ * PC218E PC272E
+ * ------------- -------------
+ * Sources 6 6
+ * 0 CTR-X1-OUT1 PPI-X-C0
+ * 1 CTR-X2-OUT1 PPI-X-C3
+ * 2 CTR-Y1-OUT1 PPI-Y-C0
+ * 3 CTR-Y2-OUT1 PPI-Y-C3
+ * 4 CTR-Z1-OUT1 PPI-Z-C0
+ * 5 CTR-Z2-OUT1 PPI-Z-C3
+ *
+ * When an interrupt source is enabled in the interrupt source enable
+ * register, a rising edge on the source signal latches the corresponding
+ * bit to 1 in the interrupt status register.
+ *
+ * When the interrupt status register value as a whole (actually, just the
+ * 6 least significant bits) goes from zero to non-zero, the board will
+ * generate an interrupt. No further interrupts will occur until the
+ * interrupt status register is cleared to zero. To clear a bit to zero in
+ * the interrupt status register, the corresponding interrupt source must
+ * be disabled in the interrupt source enable register (there is no
+ * separate interrupt clear register).
+ *
+ * The PC214E does not have an interrupt source enable register or an
+ * interrupt status register; its 'INTERRUPT' subdevice has a single
+ * channel and its interrupt source is selected by the position of jumper
+ * J5.
+ *
+ * COMMANDS
+ *
+ * The driver supports a read streaming acquisition command on the
+ * 'INTERRUPT' subdevice. The channel list selects the interrupt sources
+ * to be enabled. All channels will be sampled together (convert_src ==
+ * TRIG_NOW). The scan begins a short time after the hardware interrupt
+ * occurs, subject to interrupt latencies (scan_begin_src == TRIG_EXT,
+ * scan_begin_arg == 0). The value read from the interrupt status register
+ * is packed into a short value, one bit per requested channel, in the
+ * order they appear in the channel list.
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "amplc_dio200.h"
+
+/*
+ * Board descriptions.
+ */
+static const struct dio200_board dio200_isa_boards[] = {
+ {
+ .name = "pc212e",
+ .n_subdevs = 6,
+ .sdtype = {
+ sd_8255, sd_8254, sd_8254, sd_8254, sd_8254, sd_intr
+ },
+ .sdinfo = { 0x00, 0x08, 0x0c, 0x10, 0x14, 0x3f },
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ }, {
+ .name = "pc214e",
+ .n_subdevs = 4,
+ .sdtype = {
+ sd_8255, sd_8255, sd_8254, sd_intr
+ },
+ .sdinfo = { 0x00, 0x08, 0x10, 0x01 },
+ }, {
+ .name = "pc215e",
+ .n_subdevs = 5,
+ .sdtype = {
+ sd_8255, sd_8255, sd_8254, sd_8254, sd_intr
+ },
+ .sdinfo = { 0x00, 0x08, 0x10, 0x14, 0x3f },
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ }, {
+ .name = "pc218e",
+ .n_subdevs = 7,
+ .sdtype = {
+ sd_8254, sd_8254, sd_8255, sd_8254, sd_8254, sd_intr
+ },
+ .sdinfo = { 0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x3f },
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ }, {
+ .name = "pc272e",
+ .n_subdevs = 4,
+ .sdtype = {
+ sd_8255, sd_8255, sd_8255, sd_intr
+ },
+ .sdinfo = { 0x00, 0x08, 0x10, 0x3f },
+ .has_int_sce = true,
+ },
+};
+
+static int dio200_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x20);
+ if (ret)
+ return ret;
+
+ return amplc_dio200_common_attach(dev, it->options[1], 0);
+}
+
+static struct comedi_driver amplc_dio200_driver = {
+ .driver_name = "amplc_dio200",
+ .module = THIS_MODULE,
+ .attach = dio200_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &dio200_isa_boards[0].name,
+ .offset = sizeof(struct dio200_board),
+ .num_names = ARRAY_SIZE(dio200_isa_boards),
+};
+module_comedi_driver(amplc_dio200_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series ISA DIO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_dio200.h b/drivers/staging/comedi/drivers/amplc_dio200.h
new file mode 100644
index 000000000..d6d6a265c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200.h
@@ -0,0 +1,51 @@
+/*
+ comedi/drivers/amplc_dio.h
+
+ Header for amplc_dio200.c, amplc_dio200_common.c and
+ amplc_dio200_pci.c.
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef AMPLC_DIO200_H_INCLUDED
+#define AMPLC_DIO200_H_INCLUDED
+
+/*
+ * Subdevice types.
+ */
+enum dio200_sdtype { sd_none, sd_intr, sd_8255, sd_8254, sd_timer };
+
+#define DIO200_MAX_SUBDEVS 8
+#define DIO200_MAX_ISNS 6
+
+struct dio200_board {
+ const char *name;
+ unsigned char mainbar;
+ unsigned short n_subdevs; /* number of subdevices */
+ unsigned char sdtype[DIO200_MAX_SUBDEVS]; /* enum dio200_sdtype */
+ unsigned char sdinfo[DIO200_MAX_SUBDEVS]; /* depends on sdtype */
+ bool has_int_sce:1; /* has interrupt enable/status reg */
+ bool has_clk_gat_sce:1; /* has clock/gate selection registers */
+ bool is_pcie:1; /* has enhanced features */
+};
+
+int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq,
+ unsigned long req_irq_flags);
+
+/* Used by initialization of PCIe boards. */
+void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val);
+
+#endif
diff --git a/drivers/staging/comedi/drivers/amplc_dio200_common.c b/drivers/staging/comedi/drivers/amplc_dio200_common.c
new file mode 100644
index 000000000..d15a3dc12
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200_common.c
@@ -0,0 +1,874 @@
+/*
+ comedi/drivers/amplc_dio200_common.c
+
+ Common support code for "amplc_dio200" and "amplc_dio200_pci".
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "amplc_dio200.h"
+#include "comedi_8254.h"
+#include "8255.h" /* only for register defines */
+
+/* 200 series registers */
+#define DIO200_IO_SIZE 0x20
+#define DIO200_PCIE_IO_SIZE 0x4000
+#define DIO200_CLK_SCE(x) (0x18 + (x)) /* Group X/Y/Z clock sel reg */
+#define DIO200_GAT_SCE(x) (0x1b + (x)) /* Group X/Y/Z gate sel reg */
+#define DIO200_INT_SCE 0x1e /* Interrupt enable/status register */
+/* Extra registers for new PCIe boards */
+#define DIO200_ENHANCE 0x20 /* 1 to enable enhanced features */
+#define DIO200_VERSION 0x24 /* Hardware version register */
+#define DIO200_TS_CONFIG 0x600 /* Timestamp timer config register */
+#define DIO200_TS_COUNT 0x602 /* Timestamp timer count register */
+
+/*
+ * Functions for constructing value for DIO_200_?CLK_SCE and
+ * DIO_200_?GAT_SCE registers:
+ *
+ * 'which' is: 0 for CTR-X1, CTR-Y1, CTR-Z1; 1 for CTR-X2, CTR-Y2 or CTR-Z2.
+ * 'chan' is the channel: 0, 1 or 2.
+ * 'source' is the signal source: 0 to 7, or 0 to 31 for "enhanced" boards.
+ */
+static unsigned char clk_gat_sce(unsigned int which, unsigned int chan,
+ unsigned int source)
+{
+ return (which << 5) | (chan << 3) |
+ ((source & 030) << 3) | (source & 007);
+}
+
+static unsigned char clk_sce(unsigned int which, unsigned int chan,
+ unsigned int source)
+{
+ return clk_gat_sce(which, chan, source);
+}
+
+static unsigned char gat_sce(unsigned int which, unsigned int chan,
+ unsigned int source)
+{
+ return clk_gat_sce(which, chan, source);
+}
+
+/*
+ * Periods of the internal clock sources in nanoseconds.
+ */
+static const unsigned int clock_period[32] = {
+ [1] = 100, /* 10 MHz */
+ [2] = 1000, /* 1 MHz */
+ [3] = 10000, /* 100 kHz */
+ [4] = 100000, /* 10 kHz */
+ [5] = 1000000, /* 1 kHz */
+ [11] = 50, /* 20 MHz (enhanced boards) */
+ /* clock sources 12 and later reserved for enhanced boards */
+};
+
+/*
+ * Timestamp timer configuration register (for new PCIe boards).
+ */
+#define TS_CONFIG_RESET 0x100 /* Reset counter to zero. */
+#define TS_CONFIG_CLK_SRC_MASK 0x0FF /* Clock source. */
+#define TS_CONFIG_MAX_CLK_SRC 2 /* Maximum clock source value. */
+
+/*
+ * Periods of the timestamp timer clock sources in nanoseconds.
+ */
+static const unsigned int ts_clock_period[TS_CONFIG_MAX_CLK_SRC + 1] = {
+ 1, /* 1 nanosecond (but with 20 ns granularity). */
+ 1000, /* 1 microsecond. */
+ 1000000, /* 1 millisecond. */
+};
+
+struct dio200_subdev_8255 {
+ unsigned int ofs; /* DIO base offset */
+};
+
+struct dio200_subdev_intr {
+ spinlock_t spinlock;
+ unsigned int ofs;
+ unsigned int valid_isns;
+ unsigned int enabled_isns;
+ bool active:1;
+};
+
+static unsigned char dio200_read8(struct comedi_device *dev,
+ unsigned int offset)
+{
+ const struct dio200_board *board = dev->board_ptr;
+
+ if (board->is_pcie)
+ offset <<= 3;
+
+ if (dev->mmio)
+ return readb(dev->mmio + offset);
+ return inb(dev->iobase + offset);
+}
+
+static void dio200_write8(struct comedi_device *dev,
+ unsigned int offset, unsigned char val)
+{
+ const struct dio200_board *board = dev->board_ptr;
+
+ if (board->is_pcie)
+ offset <<= 3;
+
+ if (dev->mmio)
+ writeb(val, dev->mmio + offset);
+ else
+ outb(val, dev->iobase + offset);
+}
+
+static unsigned int dio200_read32(struct comedi_device *dev,
+ unsigned int offset)
+{
+ const struct dio200_board *board = dev->board_ptr;
+
+ if (board->is_pcie)
+ offset <<= 3;
+
+ if (dev->mmio)
+ return readl(dev->mmio + offset);
+ return inl(dev->iobase + offset);
+}
+
+static void dio200_write32(struct comedi_device *dev,
+ unsigned int offset, unsigned int val)
+{
+ const struct dio200_board *board = dev->board_ptr;
+
+ if (board->is_pcie)
+ offset <<= 3;
+
+ if (dev->mmio)
+ writel(val, dev->mmio + offset);
+ else
+ outl(val, dev->iobase + offset);
+}
+
+static unsigned int dio200_subdev_8254_offset(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct comedi_8254 *i8254 = s->private;
+ unsigned int offset;
+
+ /* get the offset that was passed to comedi_8254_*_init() */
+ if (dev->mmio)
+ offset = i8254->mmio - dev->mmio;
+ else
+ offset = i8254->iobase - dev->iobase;
+
+ /* remove the shift that was added for PCIe boards */
+ if (board->is_pcie)
+ offset >>= 3;
+
+ /* this offset now works for the dio200_{read,write} helpers */
+ return offset;
+}
+
+static int dio200_subdev_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct dio200_subdev_intr *subpriv = s->private;
+
+ if (board->has_int_sce) {
+ /* Just read the interrupt status register. */
+ data[1] = dio200_read8(dev, subpriv->ofs) & subpriv->valid_isns;
+ } else {
+ /* No interrupt status register. */
+ data[0] = 0;
+ }
+
+ return insn->n;
+}
+
+static void dio200_stop_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct dio200_subdev_intr *subpriv = s->private;
+
+ subpriv->active = false;
+ subpriv->enabled_isns = 0;
+ if (board->has_int_sce)
+ dio200_write8(dev, subpriv->ofs, 0);
+}
+
+static void dio200_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct dio200_subdev_intr *subpriv = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int n;
+ unsigned isn_bits;
+
+ /* Determine interrupt sources to enable. */
+ isn_bits = 0;
+ if (cmd->chanlist) {
+ for (n = 0; n < cmd->chanlist_len; n++)
+ isn_bits |= (1U << CR_CHAN(cmd->chanlist[n]));
+ }
+ isn_bits &= subpriv->valid_isns;
+ /* Enable interrupt sources. */
+ subpriv->enabled_isns = isn_bits;
+ if (board->has_int_sce)
+ dio200_write8(dev, subpriv->ofs, isn_bits);
+}
+
+static int dio200_inttrig_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct dio200_subdev_intr *subpriv = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ s->async->inttrig = NULL;
+ if (subpriv->active)
+ dio200_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ return 1;
+}
+
+static void dio200_read_scan_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int triggered)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned short val;
+ unsigned int n, ch;
+
+ val = 0;
+ for (n = 0; n < cmd->chanlist_len; n++) {
+ ch = CR_CHAN(cmd->chanlist[n]);
+ if (triggered & (1U << ch))
+ val |= (1U << n);
+ }
+
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+}
+
+static int dio200_handle_read_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct dio200_subdev_intr *subpriv = s->private;
+ unsigned triggered;
+ unsigned intstat;
+ unsigned cur_enabled;
+ unsigned long flags;
+
+ triggered = 0;
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ if (board->has_int_sce) {
+ /*
+ * Collect interrupt sources that have triggered and disable
+ * them temporarily. Loop around until no extra interrupt
+ * sources have triggered, at which point, the valid part of
+ * the interrupt status register will read zero, clearing the
+ * cause of the interrupt.
+ *
+ * Mask off interrupt sources already seen to avoid infinite
+ * loop in case of misconfiguration.
+ */
+ cur_enabled = subpriv->enabled_isns;
+ while ((intstat = (dio200_read8(dev, subpriv->ofs) &
+ subpriv->valid_isns & ~triggered)) != 0) {
+ triggered |= intstat;
+ cur_enabled &= ~triggered;
+ dio200_write8(dev, subpriv->ofs, cur_enabled);
+ }
+ } else {
+ /*
+ * No interrupt status register. Assume the single interrupt
+ * source has triggered.
+ */
+ triggered = subpriv->enabled_isns;
+ }
+
+ if (triggered) {
+ /*
+ * Some interrupt sources have triggered and have been
+ * temporarily disabled to clear the cause of the interrupt.
+ *
+ * Reenable them NOW to minimize the time they are disabled.
+ */
+ cur_enabled = subpriv->enabled_isns;
+ if (board->has_int_sce)
+ dio200_write8(dev, subpriv->ofs, cur_enabled);
+
+ if (subpriv->active) {
+ /*
+ * The command is still active.
+ *
+ * Ignore interrupt sources that the command isn't
+ * interested in (just in case there's a race
+ * condition).
+ */
+ if (triggered & subpriv->enabled_isns)
+ /* Collect scan data. */
+ dio200_read_scan_intr(dev, s, triggered);
+ }
+ }
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ comedi_handle_events(dev, s);
+
+ return (triggered != 0);
+}
+
+static int dio200_subdev_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dio200_subdev_intr *subpriv = s->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ if (subpriv->active)
+ dio200_stop_intr(dev, s);
+
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ return 0;
+}
+
+static int dio200_subdev_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ /* if (err) return 4; */
+
+ return 0;
+}
+
+static int dio200_subdev_intr_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct dio200_subdev_intr *subpriv = s->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+
+ subpriv->active = true;
+
+ if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = dio200_inttrig_start_intr;
+ else /* TRIG_NOW */
+ dio200_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ return 0;
+}
+
+static int dio200_subdev_intr_init(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int offset,
+ unsigned valid_isns)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct dio200_subdev_intr *subpriv;
+
+ subpriv = comedi_alloc_spriv(s, sizeof(*subpriv));
+ if (!subpriv)
+ return -ENOMEM;
+
+ subpriv->ofs = offset;
+ subpriv->valid_isns = valid_isns;
+ spin_lock_init(&subpriv->spinlock);
+
+ if (board->has_int_sce)
+ /* Disable interrupt sources. */
+ dio200_write8(dev, subpriv->ofs, 0);
+
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ | SDF_PACKED;
+ if (board->has_int_sce) {
+ s->n_chan = DIO200_MAX_ISNS;
+ s->len_chanlist = DIO200_MAX_ISNS;
+ } else {
+ /* No interrupt source register. Support single channel. */
+ s->n_chan = 1;
+ s->len_chanlist = 1;
+ }
+ s->range_table = &range_digital;
+ s->maxdata = 1;
+ s->insn_bits = dio200_subdev_intr_insn_bits;
+ s->do_cmdtest = dio200_subdev_intr_cmdtest;
+ s->do_cmd = dio200_subdev_intr_cmd;
+ s->cancel = dio200_subdev_intr_cancel;
+
+ return 0;
+}
+
+static irqreturn_t dio200_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int handled;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ handled = dio200_handle_read_intr(dev, s);
+
+ return IRQ_RETVAL(handled);
+}
+
+static void dio200_subdev_8254_set_gate_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int src)
+{
+ unsigned int offset = dio200_subdev_8254_offset(dev, s);
+
+ dio200_write8(dev, DIO200_GAT_SCE(offset >> 3),
+ gat_sce((offset >> 2) & 1, chan, src));
+}
+
+static void dio200_subdev_8254_set_clock_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int src)
+{
+ unsigned int offset = dio200_subdev_8254_offset(dev, s);
+
+ dio200_write8(dev, DIO200_CLK_SCE(offset >> 3),
+ clk_sce((offset >> 2) & 1, chan, src));
+}
+
+static int dio200_subdev_8254_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct comedi_8254 *i8254 = s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int max_src = board->is_pcie ? 31 : 7;
+ unsigned int src;
+
+ if (!board->has_clk_gat_sce)
+ return -EINVAL;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SET_GATE_SRC:
+ src = data[2];
+ if (src > max_src)
+ return -EINVAL;
+
+ dio200_subdev_8254_set_gate_src(dev, s, chan, src);
+ i8254->gate_src[chan] = src;
+ break;
+ case INSN_CONFIG_GET_GATE_SRC:
+ data[2] = i8254->gate_src[chan];
+ break;
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ src = data[1];
+ if (src > max_src)
+ return -EINVAL;
+
+ dio200_subdev_8254_set_clock_src(dev, s, chan, src);
+ i8254->clock_src[chan] = src;
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ data[1] = i8254->clock_src[chan];
+ data[2] = clock_period[i8254->clock_src[chan]];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int dio200_subdev_8254_init(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int offset)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct comedi_8254 *i8254;
+ unsigned int regshift;
+ int chan;
+
+ /*
+ * PCIe boards need the offset shifted in order to get the
+ * correct base address of the timer.
+ */
+ if (board->is_pcie) {
+ offset <<= 3;
+ regshift = 3;
+ } else {
+ regshift = 0;
+ }
+
+ if (dev->mmio)
+ i8254 = comedi_8254_mm_init(dev->mmio + offset,
+ 0, I8254_IO8, regshift);
+ else
+ i8254 = comedi_8254_init(dev->iobase + offset,
+ 0, I8254_IO8, regshift);
+ if (!i8254)
+ return -ENOMEM;
+
+ comedi_8254_subdevice_init(s, i8254);
+
+ i8254->insn_config = dio200_subdev_8254_config;
+
+ /*
+ * There could be multiple timers so this driver does not
+ * use dev->pacer to save the i8254 pointer. Instead,
+ * comedi_8254_subdevice_init() saved the i8254 pointer in
+ * s->private. Set the runflag bit so that the core will
+ * automatically free it when the driver is detached.
+ */
+ s->runflags |= COMEDI_SRF_FREE_SPRIV;
+
+ /* Initialize channels. */
+ if (board->has_clk_gat_sce) {
+ for (chan = 0; chan < 3; chan++) {
+ /* Gate source 0 is VCC (logic 1). */
+ dio200_subdev_8254_set_gate_src(dev, s, chan, 0);
+ /* Clock source 0 is the dedicated clock input. */
+ dio200_subdev_8254_set_clock_src(dev, s, chan, 0);
+ }
+ }
+
+ return 0;
+}
+
+static void dio200_subdev_8255_set_dir(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dio200_subdev_8255 *subpriv = s->private;
+ int config;
+
+ config = I8255_CTRL_CW;
+ /* 1 in io_bits indicates output, 1 in config indicates input */
+ if (!(s->io_bits & 0x0000ff))
+ config |= I8255_CTRL_A_IO;
+ if (!(s->io_bits & 0x00ff00))
+ config |= I8255_CTRL_B_IO;
+ if (!(s->io_bits & 0x0f0000))
+ config |= I8255_CTRL_C_LO_IO;
+ if (!(s->io_bits & 0xf00000))
+ config |= I8255_CTRL_C_HI_IO;
+ dio200_write8(dev, subpriv->ofs + I8255_CTRL_REG, config);
+}
+
+static int dio200_subdev_8255_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dio200_subdev_8255 *subpriv = s->private;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ dio200_write8(dev, subpriv->ofs + I8255_DATA_A_REG,
+ s->state & 0xff);
+ if (mask & 0xff00)
+ dio200_write8(dev, subpriv->ofs + I8255_DATA_B_REG,
+ (s->state >> 8) & 0xff);
+ if (mask & 0xff0000)
+ dio200_write8(dev, subpriv->ofs + I8255_DATA_C_REG,
+ (s->state >> 16) & 0xff);
+ }
+
+ val = dio200_read8(dev, subpriv->ofs + I8255_DATA_A_REG);
+ val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_B_REG) << 8;
+ val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_C_REG) << 16;
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dio200_subdev_8255_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x0000ff;
+ else if (chan < 16)
+ mask = 0x00ff00;
+ else if (chan < 20)
+ mask = 0x0f0000;
+ else
+ mask = 0xf00000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ dio200_subdev_8255_set_dir(dev, s);
+
+ return insn->n;
+}
+
+static int dio200_subdev_8255_init(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int offset)
+{
+ struct dio200_subdev_8255 *subpriv;
+
+ subpriv = comedi_alloc_spriv(s, sizeof(*subpriv));
+ if (!subpriv)
+ return -ENOMEM;
+
+ subpriv->ofs = offset;
+
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->range_table = &range_digital;
+ s->maxdata = 1;
+ s->insn_bits = dio200_subdev_8255_bits;
+ s->insn_config = dio200_subdev_8255_config;
+ dio200_subdev_8255_set_dir(dev, s);
+ return 0;
+}
+
+static int dio200_subdev_timer_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int n;
+
+ for (n = 0; n < insn->n; n++)
+ data[n] = dio200_read32(dev, DIO200_TS_COUNT);
+ return n;
+}
+
+static void dio200_subdev_timer_reset(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int clock;
+
+ clock = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
+ dio200_write32(dev, DIO200_TS_CONFIG, clock | TS_CONFIG_RESET);
+ dio200_write32(dev, DIO200_TS_CONFIG, clock);
+}
+
+static void dio200_subdev_timer_get_clock_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *src,
+ unsigned int *period)
+{
+ unsigned int clk;
+
+ clk = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
+ *src = clk;
+ *period = (clk < ARRAY_SIZE(ts_clock_period)) ?
+ ts_clock_period[clk] : 0;
+}
+
+static int dio200_subdev_timer_set_clock_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int src)
+{
+ if (src > TS_CONFIG_MAX_CLK_SRC)
+ return -EINVAL;
+ dio200_write32(dev, DIO200_TS_CONFIG, src);
+ return 0;
+}
+
+static int dio200_subdev_timer_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret = 0;
+
+ switch (data[0]) {
+ case INSN_CONFIG_RESET:
+ dio200_subdev_timer_reset(dev, s);
+ break;
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ ret = dio200_subdev_timer_set_clock_src(dev, s, data[1]);
+ if (ret < 0)
+ ret = -EINVAL;
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ dio200_subdev_timer_get_clock_src(dev, s, &data[1], &data[2]);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret < 0 ? ret : insn->n;
+}
+
+void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val)
+{
+ dio200_write8(dev, DIO200_ENHANCE, val);
+}
+EXPORT_SYMBOL_GPL(amplc_dio200_set_enhance);
+
+int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq,
+ unsigned long req_irq_flags)
+{
+ const struct dio200_board *board = dev->board_ptr;
+ struct comedi_subdevice *s;
+ unsigned int n;
+ int ret;
+
+ ret = comedi_alloc_subdevices(dev, board->n_subdevs);
+ if (ret)
+ return ret;
+
+ for (n = 0; n < dev->n_subdevices; n++) {
+ s = &dev->subdevices[n];
+ switch (board->sdtype[n]) {
+ case sd_8254:
+ /* counter subdevice (8254) */
+ ret = dio200_subdev_8254_init(dev, s,
+ board->sdinfo[n]);
+ if (ret < 0)
+ return ret;
+ break;
+ case sd_8255:
+ /* digital i/o subdevice (8255) */
+ ret = dio200_subdev_8255_init(dev, s,
+ board->sdinfo[n]);
+ if (ret < 0)
+ return ret;
+ break;
+ case sd_intr:
+ /* 'INTERRUPT' subdevice */
+ if (irq && !dev->read_subdev) {
+ ret = dio200_subdev_intr_init(dev, s,
+ DIO200_INT_SCE,
+ board->sdinfo[n]);
+ if (ret < 0)
+ return ret;
+ dev->read_subdev = s;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+ break;
+ case sd_timer:
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = 1;
+ s->maxdata = 0xffffffff;
+ s->insn_read = dio200_subdev_timer_read;
+ s->insn_config = dio200_subdev_timer_config;
+ break;
+ default:
+ s->type = COMEDI_SUBD_UNUSED;
+ break;
+ }
+ }
+
+ if (irq && dev->read_subdev) {
+ if (request_irq(irq, dio200_interrupt, req_irq_flags,
+ dev->board_name, dev) >= 0) {
+ dev->irq = irq;
+ } else {
+ dev_warn(dev->class_dev,
+ "warning! irq %u unavailable!\n", irq);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(amplc_dio200_common_attach);
+
+static int __init amplc_dio200_common_init(void)
+{
+ return 0;
+}
+module_init(amplc_dio200_common_init);
+
+static void __exit amplc_dio200_common_exit(void)
+{
+}
+module_exit(amplc_dio200_common_exit);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi helper for amplc_dio200 and amplc_dio200_pci");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_dio200_pci.c b/drivers/staging/comedi/drivers/amplc_dio200_pci.c
new file mode 100644
index 000000000..d9850c917
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200_pci.c
@@ -0,0 +1,423 @@
+/* comedi/drivers/amplc_dio200_pci.c
+
+ Driver for Amplicon PCI215, PCI272, PCIe215, PCIe236, PCIe296.
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+ * Driver: amplc_dio200_pci
+ * Description: Amplicon 200 Series PCI Digital I/O
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Amplicon] PCI215 (amplc_dio200_pci), PCIe215, PCIe236,
+ * PCI272, PCIe296
+ * Updated: Mon, 18 Mar 2013 15:03:50 +0000
+ * Status: works
+ *
+ * Configuration options:
+ * none
+ *
+ * Manual configuration of PCI(e) cards is not supported; they are configured
+ * automatically.
+ *
+ * SUBDEVICES
+ *
+ * PCI215 PCIe215 PCIe236
+ * ------------- ------------- -------------
+ * Subdevices 5 8 8
+ * 0 PPI-X PPI-X PPI-X
+ * 1 PPI-Y UNUSED UNUSED
+ * 2 CTR-Z1 PPI-Y UNUSED
+ * 3 CTR-Z2 UNUSED UNUSED
+ * 4 INTERRUPT CTR-Z1 CTR-Z1
+ * 5 CTR-Z2 CTR-Z2
+ * 6 TIMER TIMER
+ * 7 INTERRUPT INTERRUPT
+ *
+ *
+ * PCI272 PCIe296
+ * ------------- -------------
+ * Subdevices 4 8
+ * 0 PPI-X PPI-X1
+ * 1 PPI-Y PPI-X2
+ * 2 PPI-Z PPI-Y1
+ * 3 INTERRUPT PPI-Y2
+ * 4 CTR-Z1
+ * 5 CTR-Z2
+ * 6 TIMER
+ * 7 INTERRUPT
+ *
+ * Each PPI is a 8255 chip providing 24 DIO channels. The DIO channels
+ * are configurable as inputs or outputs in four groups:
+ *
+ * Port A - channels 0 to 7
+ * Port B - channels 8 to 15
+ * Port CL - channels 16 to 19
+ * Port CH - channels 20 to 23
+ *
+ * Only mode 0 of the 8255 chips is supported.
+ *
+ * Each CTR is a 8254 chip providing 3 16-bit counter channels. Each
+ * channel is configured individually with INSN_CONFIG instructions. The
+ * specific type of configuration instruction is specified in data[0].
+ * Some configuration instructions expect an additional parameter in
+ * data[1]; others return a value in data[1]. The following configuration
+ * instructions are supported:
+ *
+ * INSN_CONFIG_SET_COUNTER_MODE. Sets the counter channel's mode and
+ * BCD/binary setting specified in data[1].
+ *
+ * INSN_CONFIG_8254_READ_STATUS. Reads the status register value for the
+ * counter channel into data[1].
+ *
+ * INSN_CONFIG_SET_CLOCK_SRC. Sets the counter channel's clock source as
+ * specified in data[1] (this is a hardware-specific value). Not
+ * supported on PC214E. For the other boards, valid clock sources are
+ * 0 to 7 as follows:
+ *
+ * 0. CLK n, the counter channel's dedicated CLK input from the SK1
+ * connector. (N.B. for other values, the counter channel's CLKn
+ * pin on the SK1 connector is an output!)
+ * 1. Internal 10 MHz clock.
+ * 2. Internal 1 MHz clock.
+ * 3. Internal 100 kHz clock.
+ * 4. Internal 10 kHz clock.
+ * 5. Internal 1 kHz clock.
+ * 6. OUT n-1, the output of counter channel n-1 (see note 1 below).
+ * 7. Ext Clock, the counter chip's dedicated Ext Clock input from
+ * the SK1 connector. This pin is shared by all three counter
+ * channels on the chip.
+ *
+ * For the PCIe boards, clock sources in the range 0 to 31 are allowed
+ * and the following additional clock sources are defined:
+ *
+ * 8. HIGH logic level.
+ * 9. LOW logic level.
+ * 10. "Pattern present" signal.
+ * 11. Internal 20 MHz clock.
+ *
+ * INSN_CONFIG_GET_CLOCK_SRC. Returns the counter channel's current
+ * clock source in data[1]. For internal clock sources, data[2] is set
+ * to the period in ns.
+ *
+ * INSN_CONFIG_SET_GATE_SRC. Sets the counter channel's gate source as
+ * specified in data[2] (this is a hardware-specific value). Not
+ * supported on PC214E. For the other boards, valid gate sources are 0
+ * to 7 as follows:
+ *
+ * 0. VCC (internal +5V d.c.), i.e. gate permanently enabled.
+ * 1. GND (internal 0V d.c.), i.e. gate permanently disabled.
+ * 2. GAT n, the counter channel's dedicated GAT input from the SK1
+ * connector. (N.B. for other values, the counter channel's GATn
+ * pin on the SK1 connector is an output!)
+ * 3. /OUT n-2, the inverted output of counter channel n-2 (see note
+ * 2 below).
+ * 4. Reserved.
+ * 5. Reserved.
+ * 6. Reserved.
+ * 7. Reserved.
+ *
+ * For the PCIe boards, gate sources in the range 0 to 31 are allowed;
+ * the following additional clock sources and clock sources 6 and 7 are
+ * (re)defined:
+ *
+ * 6. /GAT n, negated version of the counter channel's dedicated
+ * GAT input (negated version of gate source 2).
+ * 7. OUT n-2, the non-inverted output of counter channel n-2
+ * (negated version of gate source 3).
+ * 8. "Pattern present" signal, HIGH while pattern present.
+ * 9. "Pattern occurred" latched signal, latches HIGH when pattern
+ * occurs.
+ * 10. "Pattern gone away" latched signal, latches LOW when pattern
+ * goes away after it occurred.
+ * 11. Negated "pattern present" signal, LOW while pattern present
+ * (negated version of gate source 8).
+ * 12. Negated "pattern occurred" latched signal, latches LOW when
+ * pattern occurs (negated version of gate source 9).
+ * 13. Negated "pattern gone away" latched signal, latches LOW when
+ * pattern goes away after it occurred (negated version of gate
+ * source 10).
+ *
+ * INSN_CONFIG_GET_GATE_SRC. Returns the counter channel's current gate
+ * source in data[2].
+ *
+ * Clock and gate interconnection notes:
+ *
+ * 1. Clock source OUT n-1 is the output of the preceding channel on the
+ * same counter subdevice if n > 0, or the output of channel 2 on the
+ * preceding counter subdevice (see note 3) if n = 0.
+ *
+ * 2. Gate source /OUT n-2 is the inverted output of channel 0 on the
+ * same counter subdevice if n = 2, or the inverted output of channel n+1
+ * on the preceding counter subdevice (see note 3) if n < 2.
+ *
+ * 3. The counter subdevices are connected in a ring, so the highest
+ * counter subdevice precedes the lowest.
+ *
+ * The 'TIMER' subdevice is a free-running 32-bit timer subdevice.
+ *
+ * The 'INTERRUPT' subdevice pretends to be a digital input subdevice. The
+ * digital inputs come from the interrupt status register. The number of
+ * channels matches the number of interrupt sources. The PC214E does not
+ * have an interrupt status register; see notes on 'INTERRUPT SOURCES'
+ * below.
+ *
+ * INTERRUPT SOURCES
+ *
+ * PCI215 PCIe215 PCIe236
+ * ------------- ------------- -------------
+ * Sources 6 6 6
+ * 0 PPI-X-C0 PPI-X-C0 PPI-X-C0
+ * 1 PPI-X-C3 PPI-X-C3 PPI-X-C3
+ * 2 PPI-Y-C0 PPI-Y-C0 unused
+ * 3 PPI-Y-C3 PPI-Y-C3 unused
+ * 4 CTR-Z1-OUT1 CTR-Z1-OUT1 CTR-Z1-OUT1
+ * 5 CTR-Z2-OUT1 CTR-Z2-OUT1 CTR-Z2-OUT1
+ *
+ * PCI272 PCIe296
+ * ------------- -------------
+ * Sources 6 6
+ * 0 PPI-X-C0 PPI-X1-C0
+ * 1 PPI-X-C3 PPI-X1-C3
+ * 2 PPI-Y-C0 PPI-Y1-C0
+ * 3 PPI-Y-C3 PPI-Y1-C3
+ * 4 PPI-Z-C0 CTR-Z1-OUT1
+ * 5 PPI-Z-C3 CTR-Z2-OUT1
+ *
+ * When an interrupt source is enabled in the interrupt source enable
+ * register, a rising edge on the source signal latches the corresponding
+ * bit to 1 in the interrupt status register.
+ *
+ * When the interrupt status register value as a whole (actually, just the
+ * 6 least significant bits) goes from zero to non-zero, the board will
+ * generate an interrupt. The interrupt will remain asserted until the
+ * interrupt status register is cleared to zero. To clear a bit to zero in
+ * the interrupt status register, the corresponding interrupt source must
+ * be disabled in the interrupt source enable register (there is no
+ * separate interrupt clear register).
+ *
+ * COMMANDS
+ *
+ * The driver supports a read streaming acquisition command on the
+ * 'INTERRUPT' subdevice. The channel list selects the interrupt sources
+ * to be enabled. All channels will be sampled together (convert_src ==
+ * TRIG_NOW). The scan begins a short time after the hardware interrupt
+ * occurs, subject to interrupt latencies (scan_begin_src == TRIG_EXT,
+ * scan_begin_arg == 0). The value read from the interrupt status register
+ * is packed into a short value, one bit per requested channel, in the
+ * order they appear in the channel list.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "amplc_dio200.h"
+
+/*
+ * Board descriptions.
+ */
+
+enum dio200_pci_model {
+ pci215_model,
+ pci272_model,
+ pcie215_model,
+ pcie236_model,
+ pcie296_model
+};
+
+static const struct dio200_board dio200_pci_boards[] = {
+ [pci215_model] = {
+ .name = "pci215",
+ .mainbar = 2,
+ .n_subdevs = 5,
+ .sdtype = {
+ sd_8255, sd_8255, sd_8254, sd_8254, sd_intr
+ },
+ .sdinfo = { 0x00, 0x08, 0x10, 0x14, 0x3f },
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ },
+ [pci272_model] = {
+ .name = "pci272",
+ .mainbar = 2,
+ .n_subdevs = 4,
+ .sdtype = {
+ sd_8255, sd_8255, sd_8255, sd_intr
+ },
+ .sdinfo = { 0x00, 0x08, 0x10, 0x3f },
+ .has_int_sce = true,
+ },
+ [pcie215_model] = {
+ .name = "pcie215",
+ .mainbar = 1,
+ .n_subdevs = 8,
+ .sdtype = {
+ sd_8255, sd_none, sd_8255, sd_none,
+ sd_8254, sd_8254, sd_timer, sd_intr
+ },
+ .sdinfo = {
+ 0x00, 0x00, 0x08, 0x00, 0x10, 0x14, 0x00, 0x3f
+ },
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ .is_pcie = true,
+ },
+ [pcie236_model] = {
+ .name = "pcie236",
+ .mainbar = 1,
+ .n_subdevs = 8,
+ .sdtype = {
+ sd_8255, sd_none, sd_none, sd_none,
+ sd_8254, sd_8254, sd_timer, sd_intr
+ },
+ .sdinfo = {
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x14, 0x00, 0x3f
+ },
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ .is_pcie = true,
+ },
+ [pcie296_model] = {
+ .name = "pcie296",
+ .mainbar = 1,
+ .n_subdevs = 8,
+ .sdtype = {
+ sd_8255, sd_8255, sd_8255, sd_8255,
+ sd_8254, sd_8254, sd_timer, sd_intr
+ },
+ .sdinfo = {
+ 0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x00, 0x3f
+ },
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ .is_pcie = true,
+ },
+};
+
+/*
+ * This function does some special set-up for the PCIe boards
+ * PCIe215, PCIe236, PCIe296.
+ */
+static int dio200_pcie_board_setup(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ void __iomem *brbase;
+
+ /*
+ * The board uses Altera Cyclone IV with PCI-Express hard IP.
+ * The FPGA configuration has the PCI-Express Avalon-MM Bridge
+ * Control registers in PCI BAR 0, offset 0, and the length of
+ * these registers is 0x4000.
+ *
+ * We need to write 0x80 to the "Avalon-MM to PCI-Express Interrupt
+ * Enable" register at offset 0x50 to allow generation of PCIe
+ * interrupts when RXmlrq_i is asserted in the SOPC Builder system.
+ */
+ if (pci_resource_len(pcidev, 0) < 0x4000) {
+ dev_err(dev->class_dev, "error! bad PCI region!\n");
+ return -EINVAL;
+ }
+ brbase = pci_ioremap_bar(pcidev, 0);
+ if (!brbase) {
+ dev_err(dev->class_dev, "error! failed to map registers!\n");
+ return -ENOMEM;
+ }
+ writel(0x80, brbase + 0x50);
+ iounmap(brbase);
+ /* Enable "enhanced" features of board. */
+ amplc_dio200_set_enhance(dev, 1);
+ return 0;
+}
+
+static int dio200_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context_model)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ const struct dio200_board *board = NULL;
+ unsigned int bar;
+ int ret;
+
+ if (context_model < ARRAY_SIZE(dio200_pci_boards))
+ board = &dio200_pci_boards[context_model];
+ if (!board)
+ return -EINVAL;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ dev_info(dev->class_dev, "%s: attach pci %s (%s)\n",
+ dev->driver->driver_name, pci_name(pci_dev), dev->board_name);
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ bar = board->mainbar;
+ if (pci_resource_flags(pci_dev, bar) & IORESOURCE_MEM) {
+ dev->mmio = pci_ioremap_bar(pci_dev, bar);
+ if (!dev->mmio) {
+ dev_err(dev->class_dev,
+ "error! cannot remap registers\n");
+ return -ENOMEM;
+ }
+ } else {
+ dev->iobase = pci_resource_start(pci_dev, bar);
+ }
+
+ if (board->is_pcie) {
+ ret = dio200_pcie_board_setup(dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return amplc_dio200_common_attach(dev, pci_dev->irq, IRQF_SHARED);
+}
+
+static struct comedi_driver dio200_pci_comedi_driver = {
+ .driver_name = "amplc_dio200_pci",
+ .module = THIS_MODULE,
+ .auto_attach = dio200_pci_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static const struct pci_device_id dio200_pci_table[] = {
+ { PCI_VDEVICE(AMPLICON, 0x000b), pci215_model },
+ { PCI_VDEVICE(AMPLICON, 0x000a), pci272_model },
+ { PCI_VDEVICE(AMPLICON, 0x0011), pcie236_model },
+ { PCI_VDEVICE(AMPLICON, 0x0012), pcie215_model },
+ { PCI_VDEVICE(AMPLICON, 0x0014), pcie296_model },
+ {0}
+};
+
+MODULE_DEVICE_TABLE(pci, dio200_pci_table);
+
+static int dio200_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &dio200_pci_comedi_driver,
+ id->driver_data);
+}
+
+static struct pci_driver dio200_pci_pci_driver = {
+ .name = "amplc_dio200_pci",
+ .id_table = dio200_pci_table,
+ .probe = dio200_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(dio200_pci_comedi_driver, dio200_pci_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series PCI(e) DIO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pc236.c b/drivers/staging/comedi/drivers/amplc_pc236.c
new file mode 100644
index 000000000..875cc19cb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pc236.c
@@ -0,0 +1,85 @@
+/*
+ * comedi/drivers/amplc_pc236.c
+ * Driver for Amplicon PC36AT DIO boards.
+ *
+ * Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+/*
+ * Driver: amplc_pc236
+ * Description: Amplicon PC36AT
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Amplicon] PC36AT (pc36at)
+ * Updated: Fri, 25 Jul 2014 15:32:40 +0000
+ * Status: works
+ *
+ * Configuration options - PC36AT:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional)
+ *
+ * The PC36AT board has a single 8255 appearing as subdevice 0.
+ *
+ * Subdevice 1 pretends to be a digital input device, but it always returns
+ * 0 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
+ * a rising edge on port C bit 3 acts as an external trigger, which can be
+ * used to wake up tasks. This is like the comedi_parport device, but the
+ * only way to physically disable the interrupt on the PC36AT is to remove
+ * the IRQ jumper. If no interrupt is connected, then subdevice 1 is
+ * unused.
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+#include "amplc_pc236.h"
+
+static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct pc236_private *devpriv;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x4);
+ if (ret)
+ return ret;
+
+ return amplc_pc236_common_attach(dev, dev->iobase, it->options[1], 0);
+}
+
+static const struct pc236_board pc236_boards[] = {
+ {
+ .name = "pc36at",
+ },
+};
+
+static struct comedi_driver amplc_pc236_driver = {
+ .driver_name = "amplc_pc236",
+ .module = THIS_MODULE,
+ .attach = pc236_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pc236_boards[0].name,
+ .offset = sizeof(struct pc236_board),
+ .num_names = ARRAY_SIZE(pc236_boards),
+};
+
+module_comedi_driver(amplc_pc236_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PC36AT DIO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pc236.h b/drivers/staging/comedi/drivers/amplc_pc236.h
new file mode 100644
index 000000000..91d6d9c06
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pc236.h
@@ -0,0 +1,42 @@
+/*
+ * comedi/drivers/amplc_pc236.h
+ * Header for "amplc_pc236", "amplc_pci236" and "amplc_pc236_common".
+ *
+ * Copyright (C) 2002-2014 MEV Ltd. <http://www.mev.co.uk/>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef AMPLC_PC236_H_INCLUDED
+#define AMPLC_PC236_H_INCLUDED
+
+#include <linux/types.h>
+
+struct comedi_device;
+
+struct pc236_board {
+ const char *name;
+ void (*intr_update_cb)(struct comedi_device *dev, bool enable);
+ bool (*intr_chk_clr_cb)(struct comedi_device *dev);
+};
+
+struct pc236_private {
+ unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
+ bool enable_irq;
+};
+
+int amplc_pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
+ unsigned int irq, unsigned long req_irq_flags);
+
+#endif
diff --git a/drivers/staging/comedi/drivers/amplc_pc236_common.c b/drivers/staging/comedi/drivers/amplc_pc236_common.c
new file mode 100644
index 000000000..245f932a7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pc236_common.c
@@ -0,0 +1,200 @@
+/*
+ * comedi/drivers/amplc_pc236_common.c
+ * Common support code for "amplc_pc236" and "amplc_pci236".
+ *
+ * Copyright (C) 2002-2014 MEV Ltd. <http://www.mev.co.uk/>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "amplc_pc236.h"
+#include "8255.h"
+
+static void pc236_intr_update(struct comedi_device *dev, bool enable)
+{
+ const struct pc236_board *thisboard = dev->board_ptr;
+ struct pc236_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->enable_irq = enable;
+ if (thisboard->intr_update_cb)
+ thisboard->intr_update_cb(dev, enable);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+/*
+ * This function is called when an interrupt occurs to check whether
+ * the interrupt has been marked as enabled and was generated by the
+ * board. If so, the function prepares the hardware for the next
+ * interrupt.
+ * Returns false if the interrupt should be ignored.
+ */
+static bool pc236_intr_check(struct comedi_device *dev)
+{
+ const struct pc236_board *thisboard = dev->board_ptr;
+ struct pc236_private *devpriv = dev->private;
+ bool retval = false;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (devpriv->enable_irq) {
+ if (thisboard->intr_chk_clr_cb)
+ retval = thisboard->intr_chk_clr_cb(dev);
+ else
+ retval = true;
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return retval;
+}
+
+static int pc236_intr_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static int pc236_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check it arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ pc236_intr_update(dev, true);
+
+ return 0;
+}
+
+static int pc236_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ pc236_intr_update(dev, false);
+
+ return 0;
+}
+
+static irqreturn_t pc236_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ bool handled;
+
+ handled = pc236_intr_check(dev);
+ if (dev->attached && handled) {
+ comedi_buf_write_samples(s, &s->state, 1);
+ comedi_handle_events(dev, s);
+ }
+ return IRQ_RETVAL(handled);
+}
+
+int amplc_pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
+ unsigned int irq, unsigned long req_irq_flags)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ dev->iobase = iobase;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* digital i/o subdevice (8255) */
+ ret = subdev_8255_init(dev, s, NULL, 0x00);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[1];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_UNUSED;
+ pc236_intr_update(dev, false);
+ if (irq) {
+ if (request_irq(irq, pc236_interrupt, req_irq_flags,
+ dev->board_name, dev) >= 0) {
+ dev->irq = irq;
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pc236_intr_insn;
+ s->len_chanlist = 1;
+ s->do_cmdtest = pc236_intr_cmdtest;
+ s->do_cmd = pc236_intr_cmd;
+ s->cancel = pc236_intr_cancel;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(amplc_pc236_common_attach);
+
+static int __init amplc_pc236_common_init(void)
+{
+ return 0;
+}
+module_init(amplc_pc236_common_init);
+
+static void __exit amplc_pc236_common_exit(void)
+{
+}
+module_exit(amplc_pc236_common_exit);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi helper for amplc_pc236 and amplc_pci236");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pc263.c b/drivers/staging/comedi/drivers/amplc_pc263.c
new file mode 100644
index 000000000..b1946ce6e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pc263.c
@@ -0,0 +1,111 @@
+/*
+ comedi/drivers/amplc_pc263.c
+ Driver for Amplicon PC263 and PCI263 relay boards.
+
+ Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: amplc_pc263
+Description: Amplicon PC263
+Author: Ian Abbott <abbotti@mev.co.uk>
+Devices: [Amplicon] PC263 (pc263)
+Updated: Fri, 12 Apr 2013 15:19:36 +0100
+Status: works
+
+Configuration options:
+ [0] - I/O port base address
+
+The board appears as one subdevice, with 16 digital outputs, each
+connected to a reed-relay. Relay contacts are closed when output is 1.
+The state of the outputs can be read.
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/* PC263 registers */
+
+/*
+ * Board descriptions for Amplicon PC263.
+ */
+
+struct pc263_board {
+ const char *name;
+};
+
+static const struct pc263_board pc263_boards[] = {
+ {
+ .name = "pc263",
+ },
+};
+
+static int pc263_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase);
+ outb((s->state >> 8) & 0xff, dev->iobase + 1);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pc263_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x2);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* digital output subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pc263_do_insn_bits;
+ /* read initial relay state */
+ s->state = inb(dev->iobase) | (inb(dev->iobase + 1) << 8);
+
+ return 0;
+}
+
+static struct comedi_driver amplc_pc263_driver = {
+ .driver_name = "amplc_pc263",
+ .module = THIS_MODULE,
+ .attach = pc263_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pc263_boards[0].name,
+ .offset = sizeof(struct pc263_board),
+ .num_names = ARRAY_SIZE(pc263_boards),
+};
+
+module_comedi_driver(amplc_pc263_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PC263 relay board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pci224.c b/drivers/staging/comedi/drivers/amplc_pci224.c
new file mode 100644
index 000000000..08a918548
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci224.c
@@ -0,0 +1,1136 @@
+/*
+ * comedi/drivers/amplc_pci224.c
+ * Driver for Amplicon PCI224 and PCI234 AO boards.
+ *
+ * Copyright (C) 2005 MEV Ltd. <http://www.mev.co.uk/>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: amplc_pci224
+ * Description: Amplicon PCI224, PCI234
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Amplicon] PCI224 (amplc_pci224), PCI234
+ * Updated: Thu, 31 Jul 2014 11:08:03 +0000
+ * Status: works, but see caveats
+ *
+ * Supports:
+ *
+ * - ao_insn read/write
+ * - ao_do_cmd mode with the following sources:
+ *
+ * - start_src TRIG_INT TRIG_EXT
+ * - scan_begin_src TRIG_TIMER TRIG_EXT
+ * - convert_src TRIG_NOW
+ * - scan_end_src TRIG_COUNT
+ * - stop_src TRIG_COUNT TRIG_EXT TRIG_NONE
+ *
+ * The channel list must contain at least one channel with no repeated
+ * channels. The scan end count must equal the number of channels in
+ * the channel list.
+ *
+ * There is only one external trigger source so only one of start_src,
+ * scan_begin_src or stop_src may use TRIG_EXT.
+ *
+ * Configuration options:
+ * none
+ *
+ * Manual configuration of PCI cards is not supported; they are configured
+ * automatically.
+ *
+ * Output range selection - PCI224:
+ *
+ * Output ranges on PCI224 are partly software-selectable and partly
+ * hardware-selectable according to jumper LK1. All channels are set
+ * to the same range:
+ *
+ * - LK1 position 1-2 (factory default) corresponds to the following
+ * comedi ranges:
+ *
+ * 0: [-10V,+10V]; 1: [-5V,+5V]; 2: [-2.5V,+2.5V], 3: [-1.25V,+1.25V],
+ * 4: [0,+10V], 5: [0,+5V], 6: [0,+2.5V], 7: [0,+1.25V]
+ *
+ * - LK1 position 2-3 corresponds to the following Comedi ranges, using
+ * an external voltage reference:
+ *
+ * 0: [-Vext,+Vext],
+ * 1: [0,+Vext]
+ *
+ * Output range selection - PCI234:
+ *
+ * Output ranges on PCI234 are hardware-selectable according to jumper
+ * LK1 which affects all channels, and jumpers LK2, LK3, LK4 and LK5
+ * which affect channels 0, 1, 2 and 3 individually. LK1 chooses between
+ * an internal 5V reference and an external voltage reference (Vext).
+ * LK2/3/4/5 choose (per channel) to double the reference or not according
+ * to the following table:
+ *
+ * LK1 position LK2/3/4/5 pos Comedi range
+ * ------------- ------------- --------------
+ * 2-3 (factory) 1-2 (factory) 0: [-10V,+10V]
+ * 2-3 (factory) 2-3 1: [-5V,+5V]
+ * 1-2 1-2 (factory) 2: [-2*Vext,+2*Vext]
+ * 1-2 2-3 3: [-Vext,+Vext]
+ *
+ * Caveats:
+ *
+ * 1) All channels on the PCI224 share the same range. Any change to the
+ * range as a result of insn_write or a streaming command will affect
+ * the output voltages of all channels, including those not specified
+ * by the instruction or command.
+ *
+ * 2) For the analog output command, the first scan may be triggered
+ * falsely at the start of acquisition. This occurs when the DAC scan
+ * trigger source is switched from 'none' to 'timer' (scan_begin_src =
+ * TRIG_TIMER) or 'external' (scan_begin_src == TRIG_EXT) at the start
+ * of acquisition and the trigger source is at logic level 1 at the
+ * time of the switch. This is very likely for TRIG_TIMER. For
+ * TRIG_EXT, it depends on the state of the external line and whether
+ * the CR_INVERT flag has been set. The remaining scans are triggered
+ * correctly.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include "../comedi_pci.h"
+
+#include "comedi_8254.h"
+
+/*
+ * PCI224/234 i/o space 1 (PCIBAR2) registers.
+ */
+#define PCI224_Z2_BASE 0x14 /* 82C54 counter/timer */
+#define PCI224_ZCLK_SCE 0x1A /* Group Z Clock Configuration Register */
+#define PCI224_ZGAT_SCE 0x1D /* Group Z Gate Configuration Register */
+#define PCI224_INT_SCE 0x1E /* ISR Interrupt source mask register */
+ /* /Interrupt status */
+
+/*
+ * PCI224/234 i/o space 2 (PCIBAR3) 16-bit registers.
+ */
+#define PCI224_DACDATA 0x00 /* (w-o) DAC FIFO data. */
+#define PCI224_SOFTTRIG 0x00 /* (r-o) DAC software scan trigger. */
+#define PCI224_DACCON 0x02 /* (r/w) DAC status/configuration. */
+#define PCI224_FIFOSIZ 0x04 /* (w-o) FIFO size for wraparound mode. */
+#define PCI224_DACCEN 0x06 /* (w-o) DAC channel enable register. */
+
+/*
+ * DACCON values.
+ */
+/* (r/w) Scan trigger. */
+#define PCI224_DACCON_TRIG_MASK (7 << 0)
+#define PCI224_DACCON_TRIG_NONE (0 << 0) /* none */
+#define PCI224_DACCON_TRIG_SW (1 << 0) /* software trig */
+#define PCI224_DACCON_TRIG_EXTP (2 << 0) /* ext +ve edge */
+#define PCI224_DACCON_TRIG_EXTN (3 << 0) /* ext -ve edge */
+#define PCI224_DACCON_TRIG_Z2CT0 (4 << 0) /* Z2 CT0 out */
+#define PCI224_DACCON_TRIG_Z2CT1 (5 << 0) /* Z2 CT1 out */
+#define PCI224_DACCON_TRIG_Z2CT2 (6 << 0) /* Z2 CT2 out */
+/* (r/w) Polarity (PCI224 only, PCI234 always bipolar!). */
+#define PCI224_DACCON_POLAR_MASK (1 << 3)
+#define PCI224_DACCON_POLAR_UNI (0 << 3) /* range [0,Vref] */
+#define PCI224_DACCON_POLAR_BI (1 << 3) /* range [-Vref,Vref] */
+/* (r/w) Internal Vref (PCI224 only, when LK1 in position 1-2). */
+#define PCI224_DACCON_VREF_MASK (3 << 4)
+#define PCI224_DACCON_VREF_1_25 (0 << 4) /* Vref = 1.25V */
+#define PCI224_DACCON_VREF_2_5 (1 << 4) /* Vref = 2.5V */
+#define PCI224_DACCON_VREF_5 (2 << 4) /* Vref = 5V */
+#define PCI224_DACCON_VREF_10 (3 << 4) /* Vref = 10V */
+/* (r/w) Wraparound mode enable (to play back stored waveform). */
+#define PCI224_DACCON_FIFOWRAP (1 << 7)
+/* (r/w) FIFO enable. It MUST be set! */
+#define PCI224_DACCON_FIFOENAB (1 << 8)
+/* (r/w) FIFO interrupt trigger level (most values are not very useful). */
+#define PCI224_DACCON_FIFOINTR_MASK (7 << 9)
+#define PCI224_DACCON_FIFOINTR_EMPTY (0 << 9) /* when empty */
+#define PCI224_DACCON_FIFOINTR_NEMPTY (1 << 9) /* when not empty */
+#define PCI224_DACCON_FIFOINTR_NHALF (2 << 9) /* when not half full */
+#define PCI224_DACCON_FIFOINTR_HALF (3 << 9) /* when half full */
+#define PCI224_DACCON_FIFOINTR_NFULL (4 << 9) /* when not full */
+#define PCI224_DACCON_FIFOINTR_FULL (5 << 9) /* when full */
+/* (r-o) FIFO fill level. */
+#define PCI224_DACCON_FIFOFL_MASK (7 << 12)
+#define PCI224_DACCON_FIFOFL_EMPTY (1 << 12) /* 0 */
+#define PCI224_DACCON_FIFOFL_ONETOHALF (0 << 12) /* [1,2048] */
+#define PCI224_DACCON_FIFOFL_HALFTOFULL (4 << 12) /* [2049,4095] */
+#define PCI224_DACCON_FIFOFL_FULL (6 << 12) /* 4096 */
+/* (r-o) DAC busy flag. */
+#define PCI224_DACCON_BUSY (1 << 15)
+/* (w-o) FIFO reset. */
+#define PCI224_DACCON_FIFORESET (1 << 12)
+/* (w-o) Global reset (not sure what it does). */
+#define PCI224_DACCON_GLOBALRESET (1 << 13)
+
+/*
+ * DAC FIFO size.
+ */
+#define PCI224_FIFO_SIZE 4096
+
+/*
+ * DAC FIFO guaranteed minimum room available, depending on reported fill level.
+ * The maximum room available depends on the reported fill level and how much
+ * has been written!
+ */
+#define PCI224_FIFO_ROOM_EMPTY PCI224_FIFO_SIZE
+#define PCI224_FIFO_ROOM_ONETOHALF (PCI224_FIFO_SIZE / 2)
+#define PCI224_FIFO_ROOM_HALFTOFULL 1
+#define PCI224_FIFO_ROOM_FULL 0
+
+/*
+ * Counter/timer clock input configuration sources.
+ */
+#define CLK_CLK 0 /* reserved (channel-specific clock) */
+#define CLK_10MHZ 1 /* internal 10 MHz clock */
+#define CLK_1MHZ 2 /* internal 1 MHz clock */
+#define CLK_100KHZ 3 /* internal 100 kHz clock */
+#define CLK_10KHZ 4 /* internal 10 kHz clock */
+#define CLK_1KHZ 5 /* internal 1 kHz clock */
+#define CLK_OUTNM1 6 /* output of channel-1 modulo total */
+#define CLK_EXT 7 /* external clock */
+/* Macro to construct clock input configuration register value. */
+#define CLK_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7))
+
+/*
+ * Counter/timer gate input configuration sources.
+ */
+#define GAT_VCC 0 /* VCC (i.e. enabled) */
+#define GAT_GND 1 /* GND (i.e. disabled) */
+#define GAT_EXT 2 /* reserved (external gate input) */
+#define GAT_NOUTNM2 3 /* inverted output of channel-2 modulo total */
+/* Macro to construct gate input configuration register value. */
+#define GAT_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7))
+
+/*
+ * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for PCI224 and PCI234:
+ *
+ * Channel's Channel's
+ * clock input gate input
+ * Channel CLK_OUTNM1 GAT_NOUTNM2
+ * ------- ---------- -----------
+ * Z2-CT0 Z2-CT2-OUT /Z2-CT1-OUT
+ * Z2-CT1 Z2-CT0-OUT /Z2-CT2-OUT
+ * Z2-CT2 Z2-CT1-OUT /Z2-CT0-OUT
+ */
+
+/*
+ * Interrupt enable/status bits
+ */
+#define PCI224_INTR_EXT 0x01 /* rising edge on external input */
+#define PCI224_INTR_DAC 0x04 /* DAC (FIFO) interrupt */
+#define PCI224_INTR_Z2CT1 0x20 /* rising edge on Z2-CT1 output */
+
+#define PCI224_INTR_EDGE_BITS (PCI224_INTR_EXT | PCI224_INTR_Z2CT1)
+#define PCI224_INTR_LEVEL_BITS PCI224_INTR_DACFIFO
+
+/*
+ * Handy macros.
+ */
+
+/* Combine old and new bits. */
+#define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask)))
+
+/* Current CPU. XXX should this be hard_smp_processor_id()? */
+#define THISCPU smp_processor_id()
+
+/* State bits for use with atomic bit operations. */
+#define AO_CMD_STARTED 0
+
+/*
+ * Range tables.
+ */
+
+/*
+ * The ranges for PCI224.
+ *
+ * These are partly hardware-selectable by jumper LK1 and partly
+ * software-selectable.
+ *
+ * All channels share the same hardware range.
+ */
+static const struct comedi_lrange range_pci224 = {
+ 10, {
+ /* jumper LK1 in position 1-2 (factory default) */
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ /* jumper LK1 in position 2-3 */
+ RANGE_ext(-1, 1), /* bipolar [-Vext,+Vext] */
+ RANGE_ext(0, 1), /* unipolar [0,+Vext] */
+ }
+};
+
+static const unsigned short hwrange_pci224[10] = {
+ /* jumper LK1 in position 1-2 (factory default) */
+ PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_10,
+ PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_5,
+ PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_2_5,
+ PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_1_25,
+ PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_10,
+ PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_5,
+ PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_2_5,
+ PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_1_25,
+ /* jumper LK1 in position 2-3 */
+ PCI224_DACCON_POLAR_BI,
+ PCI224_DACCON_POLAR_UNI,
+};
+
+/* Used to check all channels set to the same range on PCI224. */
+static const unsigned char range_check_pci224[10] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+};
+
+/*
+ * The ranges for PCI234.
+ *
+ * These are all hardware-selectable by jumper LK1 affecting all channels,
+ * and jumpers LK2, LK3, LK4 and LK5 affecting channels 0, 1, 2 and 3
+ * individually.
+ */
+static const struct comedi_lrange range_pci234 = {
+ 4, {
+ /* LK1: 1-2 (fact def), LK2/3/4/5: 2-3 (fac def) */
+ BIP_RANGE(10),
+ /* LK1: 1-2 (fact def), LK2/3/4/5: 1-2 */
+ BIP_RANGE(5),
+ /* LK1: 2-3, LK2/3/4/5: 2-3 (fac def) */
+ RANGE_ext(-2, 2), /* bipolar [-2*Vext,+2*Vext] */
+ /* LK1: 2-3, LK2/3/4/5: 1-2 */
+ RANGE_ext(-1, 1), /* bipolar [-Vext,+Vext] */
+ }
+};
+
+/* N.B. PCI234 ignores the polarity bit, but software uses it. */
+static const unsigned short hwrange_pci234[4] = {
+ PCI224_DACCON_POLAR_BI,
+ PCI224_DACCON_POLAR_BI,
+ PCI224_DACCON_POLAR_BI,
+ PCI224_DACCON_POLAR_BI,
+};
+
+/* Used to check all channels use same LK1 setting on PCI234. */
+static const unsigned char range_check_pci234[4] = {
+ 0, 0, 1, 1,
+};
+
+/*
+ * Board descriptions.
+ */
+
+enum pci224_model { pci224_model, pci234_model };
+
+struct pci224_board {
+ const char *name;
+ unsigned int ao_chans;
+ unsigned int ao_bits;
+ const struct comedi_lrange *ao_range;
+ const unsigned short *ao_hwrange;
+ const unsigned char *ao_range_check;
+};
+
+static const struct pci224_board pci224_boards[] = {
+ [pci224_model] = {
+ .name = "pci224",
+ .ao_chans = 16,
+ .ao_bits = 12,
+ .ao_range = &range_pci224,
+ .ao_hwrange = &hwrange_pci224[0],
+ .ao_range_check = &range_check_pci224[0],
+ },
+ [pci234_model] = {
+ .name = "pci234",
+ .ao_chans = 4,
+ .ao_bits = 16,
+ .ao_range = &range_pci234,
+ .ao_hwrange = &hwrange_pci234[0],
+ .ao_range_check = &range_check_pci234[0],
+ },
+};
+
+struct pci224_private {
+ unsigned long iobase1;
+ unsigned long state;
+ spinlock_t ao_spinlock; /* spinlock for AO command handling */
+ unsigned short *ao_scan_vals;
+ unsigned char *ao_scan_order;
+ int intr_cpuid;
+ short intr_running;
+ unsigned short daccon;
+ unsigned short ao_enab; /* max 16 channels so 'short' will do */
+ unsigned char intsce;
+};
+
+/*
+ * Called from the 'insn_write' function to perform a single write.
+ */
+static void
+pci224_ao_set_data(struct comedi_device *dev, int chan, int range,
+ unsigned int data)
+{
+ const struct pci224_board *thisboard = dev->board_ptr;
+ struct pci224_private *devpriv = dev->private;
+ unsigned short mangled;
+
+ /* Enable the channel. */
+ outw(1 << chan, dev->iobase + PCI224_DACCEN);
+ /* Set range and reset FIFO. */
+ devpriv->daccon = COMBINE(devpriv->daccon, thisboard->ao_hwrange[range],
+ PCI224_DACCON_POLAR_MASK |
+ PCI224_DACCON_VREF_MASK);
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+ /*
+ * Mangle the data. The hardware expects:
+ * - bipolar: 16-bit 2's complement
+ * - unipolar: 16-bit unsigned
+ */
+ mangled = (unsigned short)data << (16 - thisboard->ao_bits);
+ if ((devpriv->daccon & PCI224_DACCON_POLAR_MASK) ==
+ PCI224_DACCON_POLAR_BI) {
+ mangled ^= 0x8000;
+ }
+ /* Write mangled data to the FIFO. */
+ outw(mangled, dev->iobase + PCI224_DACDATA);
+ /* Trigger the conversion. */
+ inw(dev->iobase + PCI224_SOFTTRIG);
+}
+
+static int pci224_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ pci224_ao_set_data(dev, chan, range, val);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+/*
+ * Kills a command running on the AO subdevice.
+ */
+static void pci224_ao_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci224_private *devpriv = dev->private;
+ unsigned long flags;
+
+ if (!test_and_clear_bit(AO_CMD_STARTED, &devpriv->state))
+ return;
+
+ spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ /* Kill the interrupts. */
+ devpriv->intsce = 0;
+ outb(0, devpriv->iobase1 + PCI224_INT_SCE);
+ /*
+ * Interrupt routine may or may not be running. We may or may not
+ * have been called from the interrupt routine (directly or
+ * indirectly via a comedi_events() callback routine). It's highly
+ * unlikely that we've been called from some other interrupt routine
+ * but who knows what strange things coders get up to!
+ *
+ * If the interrupt routine is currently running, wait for it to
+ * finish, unless we appear to have been called via the interrupt
+ * routine.
+ */
+ while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) {
+ spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ }
+ spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ /* Reconfigure DAC for insn_write usage. */
+ outw(0, dev->iobase + PCI224_DACCEN); /* Disable channels. */
+ devpriv->daccon =
+ COMBINE(devpriv->daccon,
+ PCI224_DACCON_TRIG_SW | PCI224_DACCON_FIFOINTR_EMPTY,
+ PCI224_DACCON_TRIG_MASK | PCI224_DACCON_FIFOINTR_MASK);
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+}
+
+/*
+ * Handles start of acquisition for the AO subdevice.
+ */
+static void pci224_ao_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci224_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+
+ set_bit(AO_CMD_STARTED, &devpriv->state);
+
+ /* Enable interrupts. */
+ spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ if (cmd->stop_src == TRIG_EXT)
+ devpriv->intsce = PCI224_INTR_EXT | PCI224_INTR_DAC;
+ else
+ devpriv->intsce = PCI224_INTR_DAC;
+
+ outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE);
+ spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+}
+
+/*
+ * Handles interrupts from the DAC FIFO.
+ */
+static void pci224_ao_handle_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci224_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int num_scans = comedi_nscans_left(s, 0);
+ unsigned int room;
+ unsigned short dacstat;
+ unsigned int i, n;
+
+ /* Determine how much room is in the FIFO (in samples). */
+ dacstat = inw(dev->iobase + PCI224_DACCON);
+ switch (dacstat & PCI224_DACCON_FIFOFL_MASK) {
+ case PCI224_DACCON_FIFOFL_EMPTY:
+ room = PCI224_FIFO_ROOM_EMPTY;
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg) {
+ /* FIFO empty at end of counted acquisition. */
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_handle_events(dev, s);
+ return;
+ }
+ break;
+ case PCI224_DACCON_FIFOFL_ONETOHALF:
+ room = PCI224_FIFO_ROOM_ONETOHALF;
+ break;
+ case PCI224_DACCON_FIFOFL_HALFTOFULL:
+ room = PCI224_FIFO_ROOM_HALFTOFULL;
+ break;
+ default:
+ room = PCI224_FIFO_ROOM_FULL;
+ break;
+ }
+ if (room >= PCI224_FIFO_ROOM_ONETOHALF) {
+ /* FIFO is less than half-full. */
+ if (num_scans == 0) {
+ /* Nothing left to put in the FIFO. */
+ dev_err(dev->class_dev, "AO buffer underrun\n");
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+ }
+ /* Determine how many new scans can be put in the FIFO. */
+ room /= cmd->chanlist_len;
+
+ /* Determine how many scans to process. */
+ if (num_scans > room)
+ num_scans = room;
+
+ /* Process scans. */
+ for (n = 0; n < num_scans; n++) {
+ comedi_buf_read_samples(s, &devpriv->ao_scan_vals[0],
+ cmd->chanlist_len);
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ outw(devpriv->ao_scan_vals[devpriv->ao_scan_order[i]],
+ dev->iobase + PCI224_DACDATA);
+ }
+ }
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg) {
+ /*
+ * Change FIFO interrupt trigger level to wait
+ * until FIFO is empty.
+ */
+ devpriv->daccon = COMBINE(devpriv->daccon,
+ PCI224_DACCON_FIFOINTR_EMPTY,
+ PCI224_DACCON_FIFOINTR_MASK);
+ outw(devpriv->daccon, dev->iobase + PCI224_DACCON);
+ }
+ if ((devpriv->daccon & PCI224_DACCON_TRIG_MASK) ==
+ PCI224_DACCON_TRIG_NONE) {
+ unsigned short trig;
+
+ /*
+ * This is the initial DAC FIFO interrupt at the
+ * start of the acquisition. The DAC's scan trigger
+ * has been set to 'none' up until now.
+ *
+ * Now that data has been written to the FIFO, the
+ * DAC's scan trigger source can be set to the
+ * correct value.
+ *
+ * BUG: The first scan will be triggered immediately
+ * if the scan trigger source is at logic level 1.
+ */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ trig = PCI224_DACCON_TRIG_Z2CT0;
+ } else {
+ /* cmd->scan_begin_src == TRIG_EXT */
+ if (cmd->scan_begin_arg & CR_INVERT)
+ trig = PCI224_DACCON_TRIG_EXTN;
+ else
+ trig = PCI224_DACCON_TRIG_EXTP;
+ }
+ devpriv->daccon =
+ COMBINE(devpriv->daccon, trig, PCI224_DACCON_TRIG_MASK);
+ outw(devpriv->daccon, dev->iobase + PCI224_DACCON);
+ }
+
+ comedi_handle_events(dev, s);
+}
+
+static int pci224_ao_inttrig_start(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+ pci224_ao_start(dev, s);
+
+ return 1;
+}
+
+static int pci224_ao_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pci224_board *thisboard = dev->board_ptr;
+ unsigned int range_check_0;
+ unsigned int chan_mask = 0;
+ int i;
+
+ range_check_0 = thisboard->ao_range_check[CR_RANGE(cmd->chanlist[0])];
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan_mask & (1 << chan)) {
+ dev_dbg(dev->class_dev,
+ "%s: entries in chanlist must contain no duplicate channels\n",
+ __func__);
+ return -EINVAL;
+ }
+ chan_mask |= 1 << chan;
+
+ if (thisboard->ao_range_check[CR_RANGE(cmd->chanlist[i])] !=
+ range_check_0) {
+ dev_dbg(dev->class_dev,
+ "%s: entries in chanlist have incompatible ranges\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+#define MAX_SCAN_PERIOD 0xFFFFFFFFU
+#define MIN_SCAN_PERIOD 2500
+#define CONVERT_PERIOD 625
+
+/*
+ * 'do_cmdtest' function for AO subdevice.
+ */
+static int
+pci224_ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_EXT | TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_EXT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /*
+ * There's only one external trigger signal (which makes these
+ * tests easier). Only one thing can use it.
+ */
+ arg = 0;
+ if (cmd->start_src & TRIG_EXT)
+ arg++;
+ if (cmd->scan_begin_src & TRIG_EXT)
+ arg++;
+ if (cmd->stop_src & TRIG_EXT)
+ arg++;
+ if (arg > 1)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /* Force to external trigger 0. */
+ if (cmd->start_arg & ~CR_FLAGS_MASK) {
+ cmd->start_arg =
+ COMBINE(cmd->start_arg, 0, ~CR_FLAGS_MASK);
+ err |= -EINVAL;
+ }
+ /* The only flag allowed is CR_EDGE, which is ignored. */
+ if (cmd->start_arg & CR_FLAGS_MASK & ~CR_EDGE) {
+ cmd->start_arg = COMBINE(cmd->start_arg, 0,
+ CR_FLAGS_MASK & ~CR_EDGE);
+ err |= -EINVAL;
+ }
+ break;
+ }
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ MAX_SCAN_PERIOD);
+
+ arg = cmd->chanlist_len * CONVERT_PERIOD;
+ if (arg < MIN_SCAN_PERIOD)
+ arg = MIN_SCAN_PERIOD;
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg);
+ break;
+ case TRIG_EXT:
+ /* Force to external trigger 0. */
+ if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) {
+ cmd->scan_begin_arg =
+ COMBINE(cmd->scan_begin_arg, 0, ~CR_FLAGS_MASK);
+ err |= -EINVAL;
+ }
+ /* Only allow flags CR_EDGE and CR_INVERT. Ignore CR_EDGE. */
+ if (cmd->scan_begin_arg & CR_FLAGS_MASK &
+ ~(CR_EDGE | CR_INVERT)) {
+ cmd->scan_begin_arg =
+ COMBINE(cmd->scan_begin_arg, 0,
+ CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT));
+ err |= -EINVAL;
+ }
+ break;
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_EXT:
+ /* Force to external trigger 0. */
+ if (cmd->stop_arg & ~CR_FLAGS_MASK) {
+ cmd->stop_arg =
+ COMBINE(cmd->stop_arg, 0, ~CR_FLAGS_MASK);
+ err |= -EINVAL;
+ }
+ /* The only flag allowed is CR_EDGE, which is ignored. */
+ if (cmd->stop_arg & CR_FLAGS_MASK & ~CR_EDGE) {
+ cmd->stop_arg =
+ COMBINE(cmd->stop_arg, 0, CR_FLAGS_MASK & ~CR_EDGE);
+ }
+ break;
+ case TRIG_NONE:
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments. */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ /* Use two timers. */
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= pci224_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void pci224_ao_start_pacer(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci224_private *devpriv = dev->private;
+
+ /*
+ * The output of timer Z2-0 will be used as the scan trigger
+ * source.
+ */
+ /* Make sure Z2-0 is gated on. */
+ outb(GAT_CONFIG(0, GAT_VCC), devpriv->iobase1 + PCI224_ZGAT_SCE);
+ /* Cascading with Z2-2. */
+ /* Make sure Z2-2 is gated on. */
+ outb(GAT_CONFIG(2, GAT_VCC), devpriv->iobase1 + PCI224_ZGAT_SCE);
+ /* Z2-2 needs 10 MHz clock. */
+ outb(CLK_CONFIG(2, CLK_10MHZ), devpriv->iobase1 + PCI224_ZCLK_SCE);
+ /* Z2-0 is clocked from Z2-2's output. */
+ outb(CLK_CONFIG(0, CLK_OUTNM1), devpriv->iobase1 + PCI224_ZCLK_SCE);
+
+ comedi_8254_pacer_enable(dev->pacer, 2, 0, false);
+}
+
+static int pci224_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct pci224_board *thisboard = dev->board_ptr;
+ struct pci224_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int range;
+ unsigned int i, j;
+ unsigned int ch;
+ unsigned int rank;
+ unsigned long flags;
+
+ /* Cannot handle null/empty chanlist. */
+ if (!cmd->chanlist || cmd->chanlist_len == 0)
+ return -EINVAL;
+
+ /* Determine which channels are enabled and their load order. */
+ devpriv->ao_enab = 0;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ ch = CR_CHAN(cmd->chanlist[i]);
+ devpriv->ao_enab |= 1U << ch;
+ rank = 0;
+ for (j = 0; j < cmd->chanlist_len; j++) {
+ if (CR_CHAN(cmd->chanlist[j]) < ch)
+ rank++;
+ }
+ devpriv->ao_scan_order[rank] = i;
+ }
+
+ /* Set enabled channels. */
+ outw(devpriv->ao_enab, dev->iobase + PCI224_DACCEN);
+
+ /* Determine range and polarity. All channels the same. */
+ range = CR_RANGE(cmd->chanlist[0]);
+
+ /*
+ * Set DAC range and polarity.
+ * Set DAC scan trigger source to 'none'.
+ * Set DAC FIFO interrupt trigger level to 'not half full'.
+ * Reset DAC FIFO.
+ *
+ * N.B. DAC FIFO interrupts are currently disabled.
+ */
+ devpriv->daccon =
+ COMBINE(devpriv->daccon,
+ thisboard->ao_hwrange[range] | PCI224_DACCON_TRIG_NONE |
+ PCI224_DACCON_FIFOINTR_NHALF,
+ PCI224_DACCON_POLAR_MASK | PCI224_DACCON_VREF_MASK |
+ PCI224_DACCON_TRIG_MASK | PCI224_DACCON_FIFOINTR_MASK);
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(dev->pacer);
+ pci224_ao_start_pacer(dev, s);
+ }
+
+ spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ if (cmd->start_src == TRIG_INT) {
+ s->async->inttrig = pci224_ao_inttrig_start;
+ } else { /* TRIG_EXT */
+ /* Enable external interrupt trigger to start acquisition. */
+ devpriv->intsce |= PCI224_INTR_EXT;
+ outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE);
+ }
+ spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+
+ return 0;
+}
+
+/*
+ * 'cancel' function for AO subdevice.
+ */
+static int pci224_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ pci224_ao_stop(dev, s);
+ return 0;
+}
+
+/*
+ * 'munge' data for AO command.
+ */
+static void
+pci224_ao_munge(struct comedi_device *dev, struct comedi_subdevice *s,
+ void *data, unsigned int num_bytes, unsigned int chan_index)
+{
+ const struct pci224_board *thisboard = dev->board_ptr;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned short *array = data;
+ unsigned int length = num_bytes / sizeof(*array);
+ unsigned int offset;
+ unsigned int shift;
+ unsigned int i;
+
+ /* The hardware expects 16-bit numbers. */
+ shift = 16 - thisboard->ao_bits;
+ /* Channels will be all bipolar or all unipolar. */
+ if ((thisboard->ao_hwrange[CR_RANGE(cmd->chanlist[0])] &
+ PCI224_DACCON_POLAR_MASK) == PCI224_DACCON_POLAR_UNI) {
+ /* Unipolar */
+ offset = 0;
+ } else {
+ /* Bipolar */
+ offset = 32768;
+ }
+ /* Munge the data. */
+ for (i = 0; i < length; i++)
+ array[i] = (array[i] << shift) - offset;
+}
+
+/*
+ * Interrupt handler.
+ */
+static irqreturn_t pci224_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pci224_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ struct comedi_cmd *cmd;
+ unsigned char intstat, valid_intstat;
+ unsigned char curenab;
+ int retval = 0;
+ unsigned long flags;
+
+ intstat = inb(devpriv->iobase1 + PCI224_INT_SCE) & 0x3F;
+ if (intstat) {
+ retval = 1;
+ spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ valid_intstat = devpriv->intsce & intstat;
+ /* Temporarily disable interrupt sources. */
+ curenab = devpriv->intsce & ~intstat;
+ outb(curenab, devpriv->iobase1 + PCI224_INT_SCE);
+ devpriv->intr_running = 1;
+ devpriv->intr_cpuid = THISCPU;
+ spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ if (valid_intstat) {
+ cmd = &s->async->cmd;
+ if (valid_intstat & PCI224_INTR_EXT) {
+ devpriv->intsce &= ~PCI224_INTR_EXT;
+ if (cmd->start_src == TRIG_EXT)
+ pci224_ao_start(dev, s);
+ else if (cmd->stop_src == TRIG_EXT)
+ pci224_ao_stop(dev, s);
+ }
+ if (valid_intstat & PCI224_INTR_DAC)
+ pci224_ao_handle_fifo(dev, s);
+ }
+ /* Reenable interrupt sources. */
+ spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ if (curenab != devpriv->intsce) {
+ outb(devpriv->intsce,
+ devpriv->iobase1 + PCI224_INT_SCE);
+ }
+ devpriv->intr_running = 0;
+ spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ }
+ return IRQ_RETVAL(retval);
+}
+
+static int
+pci224_auto_attach(struct comedi_device *dev, unsigned long context_model)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ const struct pci224_board *thisboard = NULL;
+ struct pci224_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int irq;
+ int ret;
+
+ if (context_model < ARRAY_SIZE(pci224_boards))
+ thisboard = &pci224_boards[context_model];
+ if (!thisboard || !thisboard->name) {
+ dev_err(dev->class_dev,
+ "amplc_pci224: BUG! cannot determine board type!\n");
+ return -EINVAL;
+ }
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ dev_info(dev->class_dev, "amplc_pci224: attach pci %s - %s\n",
+ pci_name(pci_dev), dev->board_name);
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ spin_lock_init(&devpriv->ao_spinlock);
+
+ devpriv->iobase1 = pci_resource_start(pci_dev, 2);
+ dev->iobase = pci_resource_start(pci_dev, 3);
+ irq = pci_dev->irq;
+
+ /* Allocate buffer to hold values for AO channel scan. */
+ devpriv->ao_scan_vals = kmalloc(sizeof(devpriv->ao_scan_vals[0]) *
+ thisboard->ao_chans, GFP_KERNEL);
+ if (!devpriv->ao_scan_vals)
+ return -ENOMEM;
+
+ /* Allocate buffer to hold AO channel scan order. */
+ devpriv->ao_scan_order = kmalloc(sizeof(devpriv->ao_scan_order[0]) *
+ thisboard->ao_chans, GFP_KERNEL);
+ if (!devpriv->ao_scan_order)
+ return -ENOMEM;
+
+ /* Disable interrupt sources. */
+ devpriv->intsce = 0;
+ outb(0, devpriv->iobase1 + PCI224_INT_SCE);
+
+ /* Initialize the DAC hardware. */
+ outw(PCI224_DACCON_GLOBALRESET, dev->iobase + PCI224_DACCON);
+ outw(0, dev->iobase + PCI224_DACCEN);
+ outw(0, dev->iobase + PCI224_FIFOSIZ);
+ devpriv->daccon = PCI224_DACCON_TRIG_SW | PCI224_DACCON_POLAR_BI |
+ PCI224_DACCON_FIFOENAB | PCI224_DACCON_FIFOINTR_EMPTY;
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+
+ dev->pacer = comedi_8254_init(devpriv->iobase1 + PCI224_Z2_BASE,
+ I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* Analog output subdevice. */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
+ s->n_chan = thisboard->ao_chans;
+ s->maxdata = (1 << thisboard->ao_bits) - 1;
+ s->range_table = thisboard->ao_range;
+ s->insn_write = pci224_ao_insn_write;
+ s->len_chanlist = s->n_chan;
+ dev->write_subdev = s;
+ s->do_cmd = pci224_ao_cmd;
+ s->do_cmdtest = pci224_ao_cmdtest;
+ s->cancel = pci224_ao_cancel;
+ s->munge = pci224_ao_munge;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ if (irq) {
+ ret = request_irq(irq, pci224_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "error! unable to allocate irq %u\n", irq);
+ return ret;
+ }
+ dev->irq = irq;
+ }
+
+ return 0;
+}
+
+static void pci224_detach(struct comedi_device *dev)
+{
+ struct pci224_private *devpriv = dev->private;
+
+ comedi_pci_detach(dev);
+ if (devpriv) {
+ kfree(devpriv->ao_scan_vals);
+ kfree(devpriv->ao_scan_order);
+ }
+}
+
+static struct comedi_driver amplc_pci224_driver = {
+ .driver_name = "amplc_pci224",
+ .module = THIS_MODULE,
+ .detach = pci224_detach,
+ .auto_attach = pci224_auto_attach,
+ .board_name = &pci224_boards[0].name,
+ .offset = sizeof(struct pci224_board),
+ .num_names = ARRAY_SIZE(pci224_boards),
+};
+
+static int amplc_pci224_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pci224_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id amplc_pci224_pci_table[] = {
+ { PCI_VDEVICE(AMPLICON, 0x0007), pci224_model },
+ { PCI_VDEVICE(AMPLICON, 0x0008), pci234_model },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, amplc_pci224_pci_table);
+
+static struct pci_driver amplc_pci224_pci_driver = {
+ .name = "amplc_pci224",
+ .id_table = amplc_pci224_pci_table,
+ .probe = amplc_pci224_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci224_driver, amplc_pci224_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PCI224 and PCI234 AO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pci230.c b/drivers/staging/comedi/drivers/amplc_pci230.c
new file mode 100644
index 000000000..20d592002
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci230.c
@@ -0,0 +1,2568 @@
+/*
+ * comedi/drivers/amplc_pci230.c
+ * Driver for Amplicon PCI230 and PCI260 Multifunction I/O boards.
+ *
+ * Copyright (C) 2001 Allan Willcox <allanwillcox@ozemail.com.au>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: amplc_pci230
+ * Description: Amplicon PCI230, PCI260 Multifunction I/O boards
+ * Author: Allan Willcox <allanwillcox@ozemail.com.au>,
+ * Steve D Sharples <steve.sharples@nottingham.ac.uk>,
+ * Ian Abbott <abbotti@mev.co.uk>
+ * Updated: Mon, 01 Sep 2014 10:09:16 +0000
+ * Devices: [Amplicon] PCI230 (amplc_pci230), PCI230+, PCI260, PCI260+
+ * Status: works
+ *
+ * Configuration options:
+ * none
+ *
+ * Manual configuration of PCI cards is not supported; they are configured
+ * automatically.
+ *
+ * The PCI230+ and PCI260+ have the same PCI device IDs as the PCI230 and
+ * PCI260, but can be distinguished by the size of the PCI regions. A
+ * card will be configured as a "+" model if detected as such.
+ *
+ * Subdevices:
+ *
+ * PCI230(+) PCI260(+)
+ * --------- ---------
+ * Subdevices 3 1
+ * 0 AI AI
+ * 1 AO
+ * 2 DIO
+ *
+ * AI Subdevice:
+ *
+ * The AI subdevice has 16 single-ended channels or 8 differential
+ * channels.
+ *
+ * The PCI230 and PCI260 cards have 12-bit resolution. The PCI230+ and
+ * PCI260+ cards have 16-bit resolution.
+ *
+ * For differential mode, use inputs 2N and 2N+1 for channel N (e.g. use
+ * inputs 14 and 15 for channel 7). If the card is physically a PCI230
+ * or PCI260 then it actually uses a "pseudo-differential" mode where the
+ * inputs are sampled a few microseconds apart. The PCI230+ and PCI260+
+ * use true differential sampling. Another difference is that if the
+ * card is physically a PCI230 or PCI260, the inverting input is 2N,
+ * whereas for a PCI230+ or PCI260+ the inverting input is 2N+1. So if a
+ * PCI230 is physically replaced by a PCI230+ (or a PCI260 with a
+ * PCI260+) and differential mode is used, the differential inputs need
+ * to be physically swapped on the connector.
+ *
+ * The following input ranges are supported:
+ *
+ * 0 => [-10, +10] V
+ * 1 => [-5, +5] V
+ * 2 => [-2.5, +2.5] V
+ * 3 => [-1.25, +1.25] V
+ * 4 => [0, 10] V
+ * 5 => [0, 5] V
+ * 6 => [0, 2.5] V
+ *
+ * AI Commands:
+ *
+ * +=========+==============+===========+============+==========+
+ * |start_src|scan_begin_src|convert_src|scan_end_src| stop_src |
+ * +=========+==============+===========+============+==========+
+ * |TRIG_NOW | TRIG_FOLLOW |TRIG_TIMER | TRIG_COUNT |TRIG_NONE |
+ * |TRIG_INT | |TRIG_EXT(3)| |TRIG_COUNT|
+ * | | |TRIG_INT | | |
+ * | |--------------|-----------| | |
+ * | | TRIG_TIMER(1)|TRIG_TIMER | | |
+ * | | TRIG_EXT(2) | | | |
+ * | | TRIG_INT | | | |
+ * +---------+--------------+-----------+------------+----------+
+ *
+ * Note 1: If AI command and AO command are used simultaneously, only
+ * one may have scan_begin_src == TRIG_TIMER.
+ *
+ * Note 2: For PCI230 and PCI230+, scan_begin_src == TRIG_EXT uses
+ * DIO channel 16 (pin 49) which will need to be configured as
+ * a digital input. For PCI260+, the EXTTRIG/EXTCONVCLK input
+ * (pin 17) is used instead. For PCI230, scan_begin_src ==
+ * TRIG_EXT is not supported. The trigger is a rising edge
+ * on the input.
+ *
+ * Note 3: For convert_src == TRIG_EXT, the EXTTRIG/EXTCONVCLK input
+ * (pin 25 on PCI230(+), pin 17 on PCI260(+)) is used. The
+ * convert_arg value is interpreted as follows:
+ *
+ * convert_arg == (CR_EDGE | 0) => rising edge
+ * convert_arg == (CR_EDGE | CR_INVERT | 0) => falling edge
+ * convert_arg == 0 => falling edge (backwards compatibility)
+ * convert_arg == 1 => rising edge (backwards compatibility)
+ *
+ * All entries in the channel list must use the same analogue reference.
+ * If the analogue reference is not AREF_DIFF (not differential) each
+ * pair of channel numbers (0 and 1, 2 and 3, etc.) must use the same
+ * input range. The input ranges used in the sequence must be all
+ * bipolar (ranges 0 to 3) or all unipolar (ranges 4 to 6). The channel
+ * sequence must consist of 1 or more identical subsequences. Within the
+ * subsequence, channels must be in ascending order with no repeated
+ * channels. For example, the following sequences are valid: 0 1 2 3
+ * (single valid subsequence), 0 2 3 5 0 2 3 5 (repeated valid
+ * subsequence), 1 1 1 1 (repeated valid subsequence). The following
+ * sequences are invalid: 0 3 2 1 (invalid subsequence), 0 2 3 5 0 2 3
+ * (incompletely repeated subsequence). Some versions of the PCI230+ and
+ * PCI260+ have a bug that requires a subsequence longer than one entry
+ * long to include channel 0.
+ *
+ * AO Subdevice:
+ *
+ * The AO subdevice has 2 channels with 12-bit resolution.
+ * The following output ranges are supported:
+ * 0 => [0, 10] V
+ * 1 => [-10, +10] V
+ *
+ * AO Commands:
+ *
+ * +=========+==============+===========+============+==========+
+ * |start_src|scan_begin_src|convert_src|scan_end_src| stop_src |
+ * +=========+==============+===========+============+==========+
+ * |TRIG_INT | TRIG_TIMER(1)| TRIG_NOW | TRIG_COUNT |TRIG_NONE |
+ * | | TRIG_EXT(2) | | |TRIG_COUNT|
+ * | | TRIG_INT | | | |
+ * +---------+--------------+-----------+------------+----------+
+ *
+ * Note 1: If AI command and AO command are used simultaneously, only
+ * one may have scan_begin_src == TRIG_TIMER.
+ *
+ * Note 2: scan_begin_src == TRIG_EXT is only supported if the card is
+ * configured as a PCI230+ and is only supported on later
+ * versions of the card. As a card configured as a PCI230+ is
+ * not guaranteed to support external triggering, please consider
+ * this support to be a bonus. It uses the EXTTRIG/ EXTCONVCLK
+ * input (PCI230+ pin 25). Triggering will be on the rising edge
+ * unless the CR_INVERT flag is set in scan_begin_arg.
+ *
+ * The channels in the channel sequence must be in ascending order with
+ * no repeats. All entries in the channel sequence must use the same
+ * output range.
+ *
+ * DIO Subdevice:
+ *
+ * The DIO subdevice is a 8255 chip providing 24 DIO channels. The DIO
+ * channels are configurable as inputs or outputs in four groups:
+ *
+ * Port A - channels 0 to 7
+ * Port B - channels 8 to 15
+ * Port CL - channels 16 to 19
+ * Port CH - channels 20 to 23
+ *
+ * Only mode 0 of the 8255 chip is supported.
+ *
+ * Bit 0 of port C (DIO channel 16) is also used as an external scan
+ * trigger input for AI commands on PCI230 and PCI230+, so would need to
+ * be configured as an input to use it for that purpose.
+ */
+
+/*
+ * Extra triggered scan functionality, interrupt bug-fix added by Steve
+ * Sharples. Support for PCI230+/260+, more triggered scan functionality,
+ * and workarounds for (or detection of) various hardware problems added
+ * by Ian Abbott.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "8255.h"
+
+/*
+ * PCI230 PCI configuration register information
+ */
+#define PCI_DEVICE_ID_PCI230 0x0000
+#define PCI_DEVICE_ID_PCI260 0x0006
+
+/*
+ * PCI230 i/o space 1 registers.
+ */
+#define PCI230_PPI_X_BASE 0x00 /* User PPI (82C55) base */
+#define PCI230_PPI_X_A 0x00 /* User PPI (82C55) port A */
+#define PCI230_PPI_X_B 0x01 /* User PPI (82C55) port B */
+#define PCI230_PPI_X_C 0x02 /* User PPI (82C55) port C */
+#define PCI230_PPI_X_CMD 0x03 /* User PPI (82C55) control word */
+#define PCI230_Z2_CT_BASE 0x14 /* 82C54 counter/timer base */
+#define PCI230_ZCLK_SCE 0x1A /* Group Z Clock Configuration */
+#define PCI230_ZGAT_SCE 0x1D /* Group Z Gate Configuration */
+#define PCI230_INT_SCE 0x1E /* Interrupt source mask (w) */
+#define PCI230_INT_STAT 0x1E /* Interrupt status (r) */
+
+/*
+ * PCI230 i/o space 2 registers.
+ */
+#define PCI230_DACCON 0x00 /* DAC control */
+#define PCI230_DACOUT1 0x02 /* DAC channel 0 (w) */
+#define PCI230_DACOUT2 0x04 /* DAC channel 1 (w) (not FIFO mode) */
+#define PCI230_ADCDATA 0x08 /* ADC data (r) */
+#define PCI230_ADCSWTRIG 0x08 /* ADC software trigger (w) */
+#define PCI230_ADCCON 0x0A /* ADC control */
+#define PCI230_ADCEN 0x0C /* ADC channel enable bits */
+#define PCI230_ADCG 0x0E /* ADC gain control bits */
+/* PCI230+ i/o space 2 additional registers. */
+#define PCI230P_ADCTRIG 0x10 /* ADC start acquisition trigger */
+#define PCI230P_ADCTH 0x12 /* ADC analog trigger threshold */
+#define PCI230P_ADCFFTH 0x14 /* ADC FIFO interrupt threshold */
+#define PCI230P_ADCFFLEV 0x16 /* ADC FIFO level (r) */
+#define PCI230P_ADCPTSC 0x18 /* ADC pre-trigger sample count (r) */
+#define PCI230P_ADCHYST 0x1A /* ADC analog trigger hysteresys */
+#define PCI230P_EXTFUNC 0x1C /* Extended functions */
+#define PCI230P_HWVER 0x1E /* Hardware version (r) */
+/* PCI230+ hardware version 2 onwards. */
+#define PCI230P2_DACDATA 0x02 /* DAC data (FIFO mode) (w) */
+#define PCI230P2_DACSWTRIG 0x02 /* DAC soft trigger (FIFO mode) (r) */
+#define PCI230P2_DACEN 0x06 /* DAC channel enable (FIFO mode) */
+
+/*
+ * DACCON read-write values.
+ */
+#define PCI230_DAC_OR_UNI (0 << 0) /* Output range unipolar */
+#define PCI230_DAC_OR_BIP (1 << 0) /* Output range bipolar */
+#define PCI230_DAC_OR_MASK (1 << 0)
+/*
+ * The following applies only if DAC FIFO support is enabled in the EXTFUNC
+ * register (and only for PCI230+ hardware version 2 onwards).
+ */
+#define PCI230P2_DAC_FIFO_EN (1 << 8) /* FIFO enable */
+/*
+ * The following apply only if the DAC FIFO is enabled (and only for PCI230+
+ * hardware version 2 onwards).
+ */
+#define PCI230P2_DAC_TRIG_NONE (0 << 2) /* No trigger */
+#define PCI230P2_DAC_TRIG_SW (1 << 2) /* Software trigger trigger */
+#define PCI230P2_DAC_TRIG_EXTP (2 << 2) /* EXTTRIG +ve edge trigger */
+#define PCI230P2_DAC_TRIG_EXTN (3 << 2) /* EXTTRIG -ve edge trigger */
+#define PCI230P2_DAC_TRIG_Z2CT0 (4 << 2) /* CT0-OUT +ve edge trigger */
+#define PCI230P2_DAC_TRIG_Z2CT1 (5 << 2) /* CT1-OUT +ve edge trigger */
+#define PCI230P2_DAC_TRIG_Z2CT2 (6 << 2) /* CT2-OUT +ve edge trigger */
+#define PCI230P2_DAC_TRIG_MASK (7 << 2)
+#define PCI230P2_DAC_FIFO_WRAP (1 << 7) /* FIFO wraparound mode */
+#define PCI230P2_DAC_INT_FIFO_EMPTY (0 << 9) /* FIFO interrupt empty */
+#define PCI230P2_DAC_INT_FIFO_NEMPTY (1 << 9)
+#define PCI230P2_DAC_INT_FIFO_NHALF (2 << 9) /* FIFO intr not half full */
+#define PCI230P2_DAC_INT_FIFO_HALF (3 << 9)
+#define PCI230P2_DAC_INT_FIFO_NFULL (4 << 9) /* FIFO interrupt not full */
+#define PCI230P2_DAC_INT_FIFO_FULL (5 << 9)
+#define PCI230P2_DAC_INT_FIFO_MASK (7 << 9)
+
+/*
+ * DACCON read-only values.
+ */
+#define PCI230_DAC_BUSY (1 << 1) /* DAC busy. */
+/*
+ * The following apply only if the DAC FIFO is enabled (and only for PCI230+
+ * hardware version 2 onwards).
+ */
+#define PCI230P2_DAC_FIFO_UNDERRUN_LATCHED (1 << 5) /* Underrun error */
+#define PCI230P2_DAC_FIFO_EMPTY (1 << 13) /* FIFO empty */
+#define PCI230P2_DAC_FIFO_FULL (1 << 14) /* FIFO full */
+#define PCI230P2_DAC_FIFO_HALF (1 << 15) /* FIFO half full */
+
+/*
+ * DACCON write-only, transient values.
+ */
+/*
+ * The following apply only if the DAC FIFO is enabled (and only for PCI230+
+ * hardware version 2 onwards).
+ */
+#define PCI230P2_DAC_FIFO_UNDERRUN_CLEAR (1 << 5) /* Clear underrun */
+#define PCI230P2_DAC_FIFO_RESET (1 << 12) /* FIFO reset */
+
+/*
+ * PCI230+ hardware version 2 DAC FIFO levels.
+ */
+#define PCI230P2_DAC_FIFOLEVEL_HALF 512
+#define PCI230P2_DAC_FIFOLEVEL_FULL 1024
+/* Free space in DAC FIFO. */
+#define PCI230P2_DAC_FIFOROOM_EMPTY PCI230P2_DAC_FIFOLEVEL_FULL
+#define PCI230P2_DAC_FIFOROOM_ONETOHALF \
+ (PCI230P2_DAC_FIFOLEVEL_FULL - PCI230P2_DAC_FIFOLEVEL_HALF)
+#define PCI230P2_DAC_FIFOROOM_HALFTOFULL 1
+#define PCI230P2_DAC_FIFOROOM_FULL 0
+
+/*
+ * ADCCON read/write values.
+ */
+#define PCI230_ADC_TRIG_NONE (0 << 0) /* No trigger */
+#define PCI230_ADC_TRIG_SW (1 << 0) /* Software trigger trigger */
+#define PCI230_ADC_TRIG_EXTP (2 << 0) /* EXTTRIG +ve edge trigger */
+#define PCI230_ADC_TRIG_EXTN (3 << 0) /* EXTTRIG -ve edge trigger */
+#define PCI230_ADC_TRIG_Z2CT0 (4 << 0) /* CT0-OUT +ve edge trigger */
+#define PCI230_ADC_TRIG_Z2CT1 (5 << 0) /* CT1-OUT +ve edge trigger */
+#define PCI230_ADC_TRIG_Z2CT2 (6 << 0) /* CT2-OUT +ve edge trigger */
+#define PCI230_ADC_TRIG_MASK (7 << 0)
+#define PCI230_ADC_IR_UNI (0 << 3) /* Input range unipolar */
+#define PCI230_ADC_IR_BIP (1 << 3) /* Input range bipolar */
+#define PCI230_ADC_IR_MASK (1 << 3)
+#define PCI230_ADC_IM_SE (0 << 4) /* Input mode single ended */
+#define PCI230_ADC_IM_DIF (1 << 4) /* Input mode differential */
+#define PCI230_ADC_IM_MASK (1 << 4)
+#define PCI230_ADC_FIFO_EN (1 << 8) /* FIFO enable */
+#define PCI230_ADC_INT_FIFO_EMPTY (0 << 9)
+#define PCI230_ADC_INT_FIFO_NEMPTY (1 << 9) /* FIFO interrupt not empty */
+#define PCI230_ADC_INT_FIFO_NHALF (2 << 9)
+#define PCI230_ADC_INT_FIFO_HALF (3 << 9) /* FIFO interrupt half full */
+#define PCI230_ADC_INT_FIFO_NFULL (4 << 9)
+#define PCI230_ADC_INT_FIFO_FULL (5 << 9) /* FIFO interrupt full */
+#define PCI230P_ADC_INT_FIFO_THRESH (7 << 9) /* FIFO interrupt threshold */
+#define PCI230_ADC_INT_FIFO_MASK (7 << 9)
+
+/*
+ * ADCCON write-only, transient values.
+ */
+#define PCI230_ADC_FIFO_RESET (1 << 12) /* FIFO reset */
+#define PCI230_ADC_GLOB_RESET (1 << 13) /* Global reset */
+
+/*
+ * ADCCON read-only values.
+ */
+#define PCI230_ADC_BUSY (1 << 15) /* ADC busy */
+#define PCI230_ADC_FIFO_EMPTY (1 << 12) /* FIFO empty */
+#define PCI230_ADC_FIFO_FULL (1 << 13) /* FIFO full */
+#define PCI230_ADC_FIFO_HALF (1 << 14) /* FIFO half full */
+#define PCI230_ADC_FIFO_FULL_LATCHED (1 << 5) /* FIFO overrun occurred */
+
+/*
+ * PCI230 ADC FIFO levels.
+ */
+#define PCI230_ADC_FIFOLEVEL_HALFFULL 2049 /* Value for FIFO half full */
+#define PCI230_ADC_FIFOLEVEL_FULL 4096 /* FIFO size */
+
+/*
+ * PCI230+ EXTFUNC values.
+ */
+/* Route EXTTRIG pin to external gate inputs. */
+#define PCI230P_EXTFUNC_GAT_EXTTRIG (1 << 0)
+/* PCI230+ hardware version 2 values. */
+/* Allow DAC FIFO to be enabled. */
+#define PCI230P2_EXTFUNC_DACFIFO (1 << 1)
+
+/*
+ * Counter/timer clock input configuration sources.
+ */
+#define CLK_CLK 0 /* reserved (channel-specific clock) */
+#define CLK_10MHZ 1 /* internal 10 MHz clock */
+#define CLK_1MHZ 2 /* internal 1 MHz clock */
+#define CLK_100KHZ 3 /* internal 100 kHz clock */
+#define CLK_10KHZ 4 /* internal 10 kHz clock */
+#define CLK_1KHZ 5 /* internal 1 kHz clock */
+#define CLK_OUTNM1 6 /* output of channel-1 modulo total */
+#define CLK_EXT 7 /* external clock */
+/* Macro to construct clock input configuration register value. */
+#define CLK_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7))
+
+/*
+ * Counter/timer gate input configuration sources.
+ */
+#define GAT_VCC 0 /* VCC (i.e. enabled) */
+#define GAT_GND 1 /* GND (i.e. disabled) */
+#define GAT_EXT 2 /* external gate input (PPCn on PCI230) */
+#define GAT_NOUTNM2 3 /* inverted output of channel-2 modulo total */
+/* Macro to construct gate input configuration register value. */
+#define GAT_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7))
+
+/*
+ * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for PCI230 and PCI260:
+ *
+ * Channel's Channel's
+ * clock input gate input
+ * Channel CLK_OUTNM1 GAT_NOUTNM2
+ * ------- ---------- -----------
+ * Z2-CT0 Z2-CT2-OUT /Z2-CT1-OUT
+ * Z2-CT1 Z2-CT0-OUT /Z2-CT2-OUT
+ * Z2-CT2 Z2-CT1-OUT /Z2-CT0-OUT
+ */
+
+/*
+ * Interrupt enables/status register values.
+ */
+#define PCI230_INT_DISABLE 0
+#define PCI230_INT_PPI_C0 (1 << 0)
+#define PCI230_INT_PPI_C3 (1 << 1)
+#define PCI230_INT_ADC (1 << 2)
+#define PCI230_INT_ZCLK_CT1 (1 << 5)
+/* For PCI230+ hardware version 2 when DAC FIFO enabled. */
+#define PCI230P2_INT_DAC (1 << 4)
+
+/*
+ * (Potentially) shared resources and their owners
+ */
+enum {
+ RES_Z2CT0 = (1U << 0), /* Z2-CT0 */
+ RES_Z2CT1 = (1U << 1), /* Z2-CT1 */
+ RES_Z2CT2 = (1U << 2) /* Z2-CT2 */
+};
+
+enum {
+ OWNER_AICMD, /* Owned by AI command */
+ OWNER_AOCMD, /* Owned by AO command */
+ NUM_OWNERS /* Number of owners */
+};
+
+/*
+ * Handy macros.
+ */
+
+/* Combine old and new bits. */
+#define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask)))
+
+/* Current CPU. XXX should this be hard_smp_processor_id()? */
+#define THISCPU smp_processor_id()
+
+/*
+ * Board descriptions for the two boards supported.
+ */
+
+struct pci230_board {
+ const char *name;
+ unsigned short id;
+ unsigned char ai_bits;
+ unsigned char ao_bits;
+ unsigned char min_hwver; /* Minimum hardware version supported. */
+ bool have_dio:1;
+};
+
+static const struct pci230_board pci230_boards[] = {
+ {
+ .name = "pci230+",
+ .id = PCI_DEVICE_ID_PCI230,
+ .ai_bits = 16,
+ .ao_bits = 12,
+ .have_dio = true,
+ .min_hwver = 1,
+ },
+ {
+ .name = "pci260+",
+ .id = PCI_DEVICE_ID_PCI260,
+ .ai_bits = 16,
+ .min_hwver = 1,
+ },
+ {
+ .name = "pci230",
+ .id = PCI_DEVICE_ID_PCI230,
+ .ai_bits = 12,
+ .ao_bits = 12,
+ .have_dio = true,
+ },
+ {
+ .name = "pci260",
+ .id = PCI_DEVICE_ID_PCI260,
+ .ai_bits = 12,
+ },
+};
+
+struct pci230_private {
+ spinlock_t isr_spinlock; /* Interrupt spin lock */
+ spinlock_t res_spinlock; /* Shared resources spin lock */
+ spinlock_t ai_stop_spinlock; /* Spin lock for stopping AI command */
+ spinlock_t ao_stop_spinlock; /* Spin lock for stopping AO command */
+ unsigned long daqio; /* PCI230's DAQ I/O space */
+ int intr_cpuid; /* ID of CPU running ISR */
+ unsigned short hwver; /* Hardware version (for '+' models) */
+ unsigned short adccon; /* ADCCON register value */
+ unsigned short daccon; /* DACCON register value */
+ unsigned short adcfifothresh; /* ADC FIFO threshold (PCI230+/260+) */
+ unsigned short adcg; /* ADCG register value */
+ unsigned char ier; /* Interrupt enable bits */
+ unsigned char res_owned[NUM_OWNERS]; /* Owned resources */
+ bool intr_running:1; /* Flag set in interrupt routine */
+ bool ai_bipolar:1; /* Flag AI range is bipolar */
+ bool ao_bipolar:1; /* Flag AO range is bipolar */
+ bool ai_cmd_started:1; /* Flag AI command started */
+ bool ao_cmd_started:1; /* Flag AO command started */
+};
+
+/* PCI230 clock source periods in ns */
+static const unsigned int pci230_timebase[8] = {
+ [CLK_10MHZ] = I8254_OSC_BASE_10MHZ,
+ [CLK_1MHZ] = I8254_OSC_BASE_1MHZ,
+ [CLK_100KHZ] = I8254_OSC_BASE_100KHZ,
+ [CLK_10KHZ] = I8254_OSC_BASE_10KHZ,
+ [CLK_1KHZ] = I8254_OSC_BASE_1KHZ,
+};
+
+/* PCI230 analogue input range table */
+static const struct comedi_lrange pci230_ai_range = {
+ 7, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5)
+ }
+};
+
+/* PCI230 analogue gain bits for each input range. */
+static const unsigned char pci230_ai_gain[7] = { 0, 1, 2, 3, 1, 2, 3 };
+
+/* PCI230 analogue output range table */
+static const struct comedi_lrange pci230_ao_range = {
+ 2, {
+ UNI_RANGE(10),
+ BIP_RANGE(10)
+ }
+};
+
+static unsigned short pci230_ai_read(struct comedi_device *dev)
+{
+ const struct pci230_board *thisboard = dev->board_ptr;
+ struct pci230_private *devpriv = dev->private;
+ unsigned short data;
+
+ /* Read sample. */
+ data = inw(devpriv->daqio + PCI230_ADCDATA);
+ /*
+ * PCI230 is 12 bit - stored in upper bits of 16 bit register
+ * (lower four bits reserved for expansion). PCI230+ is 16 bit AI.
+ *
+ * If a bipolar range was specified, mangle it
+ * (twos complement->straight binary).
+ */
+ if (devpriv->ai_bipolar)
+ data ^= 0x8000;
+ data >>= (16 - thisboard->ai_bits);
+ return data;
+}
+
+static unsigned short pci230_ao_mangle_datum(struct comedi_device *dev,
+ unsigned short datum)
+{
+ const struct pci230_board *thisboard = dev->board_ptr;
+ struct pci230_private *devpriv = dev->private;
+
+ /*
+ * PCI230 is 12 bit - stored in upper bits of 16 bit register (lower
+ * four bits reserved for expansion). PCI230+ is also 12 bit AO.
+ */
+ datum <<= (16 - thisboard->ao_bits);
+ /*
+ * If a bipolar range was specified, mangle it
+ * (straight binary->twos complement).
+ */
+ if (devpriv->ao_bipolar)
+ datum ^= 0x8000;
+ return datum;
+}
+
+static void pci230_ao_write_nofifo(struct comedi_device *dev,
+ unsigned short datum, unsigned int chan)
+{
+ struct pci230_private *devpriv = dev->private;
+
+ /* Write mangled datum to appropriate DACOUT register. */
+ outw(pci230_ao_mangle_datum(dev, datum),
+ devpriv->daqio + ((chan == 0) ? PCI230_DACOUT1 : PCI230_DACOUT2));
+}
+
+static void pci230_ao_write_fifo(struct comedi_device *dev,
+ unsigned short datum, unsigned int chan)
+{
+ struct pci230_private *devpriv = dev->private;
+
+ /* Write mangled datum to appropriate DACDATA register. */
+ outw(pci230_ao_mangle_datum(dev, datum),
+ devpriv->daqio + PCI230P2_DACDATA);
+}
+
+static bool pci230_claim_shared(struct comedi_device *dev,
+ unsigned char res_mask, unsigned int owner)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int o;
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&devpriv->res_spinlock, irqflags);
+ for (o = 0; o < NUM_OWNERS; o++) {
+ if (o == owner)
+ continue;
+ if (devpriv->res_owned[o] & res_mask) {
+ spin_unlock_irqrestore(&devpriv->res_spinlock,
+ irqflags);
+ return false;
+ }
+ }
+ devpriv->res_owned[owner] |= res_mask;
+ spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags);
+ return true;
+}
+
+static void pci230_release_shared(struct comedi_device *dev,
+ unsigned char res_mask, unsigned int owner)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&devpriv->res_spinlock, irqflags);
+ devpriv->res_owned[owner] &= ~res_mask;
+ spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags);
+}
+
+static void pci230_release_all_resources(struct comedi_device *dev,
+ unsigned int owner)
+{
+ pci230_release_shared(dev, (unsigned char)~0, owner);
+}
+
+static unsigned int pci230_divide_ns(uint64_t ns, unsigned int timebase,
+ unsigned int flags)
+{
+ uint64_t div;
+ unsigned int rem;
+
+ div = ns;
+ rem = do_div(div, timebase);
+ switch (flags & CMDF_ROUND_MASK) {
+ default:
+ case CMDF_ROUND_NEAREST:
+ div += (rem + (timebase / 2)) / timebase;
+ break;
+ case CMDF_ROUND_DOWN:
+ break;
+ case CMDF_ROUND_UP:
+ div += (rem + timebase - 1) / timebase;
+ break;
+ }
+ return div > UINT_MAX ? UINT_MAX : (unsigned int)div;
+}
+
+/*
+ * Given desired period in ns, returns the required internal clock source
+ * and gets the initial count.
+ */
+static unsigned int pci230_choose_clk_count(uint64_t ns, unsigned int *count,
+ unsigned int flags)
+{
+ unsigned int clk_src, cnt;
+
+ for (clk_src = CLK_10MHZ;; clk_src++) {
+ cnt = pci230_divide_ns(ns, pci230_timebase[clk_src], flags);
+ if (cnt <= 65536 || clk_src == CLK_1KHZ)
+ break;
+ }
+ *count = cnt;
+ return clk_src;
+}
+
+static void pci230_ns_to_single_timer(unsigned int *ns, unsigned int flags)
+{
+ unsigned int count;
+ unsigned int clk_src;
+
+ clk_src = pci230_choose_clk_count(*ns, &count, flags);
+ *ns = count * pci230_timebase[clk_src];
+}
+
+static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct,
+ unsigned int mode, uint64_t ns,
+ unsigned int flags)
+{
+ unsigned int clk_src;
+ unsigned int count;
+
+ /* Set mode. */
+ comedi_8254_set_mode(dev->pacer, ct, mode);
+ /* Determine clock source and count. */
+ clk_src = pci230_choose_clk_count(ns, &count, flags);
+ /* Program clock source. */
+ outb(CLK_CONFIG(ct, clk_src), dev->iobase + PCI230_ZCLK_SCE);
+ /* Set initial count. */
+ if (count >= 65536)
+ count = 0;
+
+ comedi_8254_write(dev->pacer, ct, count);
+}
+
+static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct)
+{
+ /* Counter ct, 8254 mode 1, initial count not written. */
+ comedi_8254_set_mode(dev->pacer, ct, I8254_MODE1);
+}
+
+static int pci230_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = inw(devpriv->daqio + PCI230_ADCCON);
+ if ((status & PCI230_ADC_FIFO_EMPTY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci230_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int n;
+ unsigned int chan, range, aref;
+ unsigned int gainshift;
+ unsigned short adccon, adcen;
+ int ret;
+
+ /* Unpack channel and range. */
+ chan = CR_CHAN(insn->chanspec);
+ range = CR_RANGE(insn->chanspec);
+ aref = CR_AREF(insn->chanspec);
+ if (aref == AREF_DIFF) {
+ /* Differential. */
+ if (chan >= s->n_chan / 2) {
+ dev_dbg(dev->class_dev,
+ "%s: differential channel number out of range 0 to %u\n",
+ __func__, (s->n_chan / 2) - 1);
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * Use Z2-CT2 as a conversion trigger instead of the built-in
+ * software trigger, as otherwise triggering of differential channels
+ * doesn't work properly for some versions of PCI230/260. Also set
+ * FIFO mode because the ADC busy bit only works for software triggers.
+ */
+ adccon = PCI230_ADC_TRIG_Z2CT2 | PCI230_ADC_FIFO_EN;
+ /* Set Z2-CT2 output low to avoid any false triggers. */
+ comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0);
+ devpriv->ai_bipolar = comedi_range_is_bipolar(s, range);
+ if (aref == AREF_DIFF) {
+ /* Differential. */
+ gainshift = chan * 2;
+ if (devpriv->hwver == 0) {
+ /*
+ * Original PCI230/260 expects both inputs of the
+ * differential channel to be enabled.
+ */
+ adcen = 3 << gainshift;
+ } else {
+ /*
+ * PCI230+/260+ expects only one input of the
+ * differential channel to be enabled.
+ */
+ adcen = 1 << gainshift;
+ }
+ adccon |= PCI230_ADC_IM_DIF;
+ } else {
+ /* Single ended. */
+ adcen = 1 << chan;
+ gainshift = chan & ~1;
+ adccon |= PCI230_ADC_IM_SE;
+ }
+ devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) |
+ (pci230_ai_gain[range] << gainshift);
+ if (devpriv->ai_bipolar)
+ adccon |= PCI230_ADC_IR_BIP;
+ else
+ adccon |= PCI230_ADC_IR_UNI;
+
+ /*
+ * Enable only this channel in the scan list - otherwise by default
+ * we'll get one sample from each channel.
+ */
+ outw(adcen, devpriv->daqio + PCI230_ADCEN);
+
+ /* Set gain for channel. */
+ outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG);
+
+ /* Specify uni/bip, se/diff, conversion source, and reset FIFO. */
+ devpriv->adccon = adccon;
+ outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON);
+
+ /* Convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /*
+ * Trigger conversion by toggling Z2-CT2 output
+ * (finish with output high).
+ */
+ comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0);
+ comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, pci230_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ data[n] = pci230_ai_read(dev);
+ }
+
+ /* return the number of samples read/written */
+ return n;
+}
+
+static int pci230_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ /*
+ * Set range - see analogue output range table; 0 => unipolar 10V,
+ * 1 => bipolar +/-10V range scale
+ */
+ devpriv->ao_bipolar = comedi_range_is_bipolar(s, range);
+ outw(range, devpriv->daqio + PCI230_DACCON);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ pci230_ao_write_nofifo(dev, val, chan);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pci230_ao_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int prev_chan = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan < prev_chan) {
+ dev_dbg(dev->class_dev,
+ "%s: channel numbers must increase\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "%s: channels must have the same range\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ prev_chan = chan;
+ }
+
+ return 0;
+}
+
+static int pci230_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct pci230_board *thisboard = dev->board_ptr;
+ struct pci230_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
+
+ tmp = TRIG_TIMER | TRIG_INT;
+ if (thisboard->min_hwver > 0 && devpriv->hwver >= 2) {
+ /*
+ * For PCI230+ hardware version 2 onwards, allow external
+ * trigger from EXTTRIG/EXTCONVCLK input (PCI230+ pin 25).
+ *
+ * FIXME: The permitted scan_begin_src values shouldn't depend
+ * on devpriv->hwver (the detected card's actual hardware
+ * version). They should only depend on thisboard->min_hwver
+ * (the static capabilities of the configured card). To fix
+ * it, a new card model, e.g. "pci230+2" would have to be
+ * defined with min_hwver set to 2. It doesn't seem worth it
+ * for this alone. At the moment, please consider
+ * scan_begin_src==TRIG_EXT support to be a bonus rather than a
+ * guarantee!
+ */
+ tmp |= TRIG_EXT;
+ }
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, tmp);
+
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SPEED_AO 8000 /* 8000 ns => 125 kHz */
+/*
+ * Comedi limit due to unsigned int cmd. Driver limit =
+ * 2^16 (16bit * counter) * 1000000ns (1kHz onboard clock) = 65.536s
+ */
+#define MIN_SPEED_AO 4294967295u /* 4294967295ns = 4.29s */
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED_AO);
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ MIN_SPEED_AO);
+ break;
+ case TRIG_EXT:
+ /*
+ * External trigger - for PCI230+ hardware version 2 onwards.
+ */
+ /* Trigger number must be 0. */
+ if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) {
+ cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
+ ~CR_FLAGS_MASK);
+ err |= -EINVAL;
+ }
+ /*
+ * The only flags allowed are CR_EDGE and CR_INVERT.
+ * The CR_EDGE flag is ignored.
+ */
+ if (cmd->scan_begin_arg & CR_FLAGS_MASK &
+ ~(CR_EDGE | CR_INVERT)) {
+ cmd->scan_begin_arg =
+ COMBINE(cmd->scan_begin_arg, 0,
+ CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT));
+ err |= -EINVAL;
+ }
+ break;
+ default:
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ break;
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ tmp = cmd->scan_begin_arg;
+ pci230_ns_to_single_timer(&cmd->scan_begin_arg, cmd->flags);
+ if (tmp != cmd->scan_begin_arg)
+ err++;
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= pci230_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void pci230_ao_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned long irqflags;
+ unsigned char intsrc;
+ bool started;
+ struct comedi_cmd *cmd;
+
+ spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags);
+ started = devpriv->ao_cmd_started;
+ devpriv->ao_cmd_started = false;
+ spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
+ if (!started)
+ return;
+ cmd = &s->async->cmd;
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* Stop scan rate generator. */
+ pci230_cancel_ct(dev, 1);
+ }
+ /* Determine interrupt source. */
+ if (devpriv->hwver < 2) {
+ /* Not using DAC FIFO. Using CT1 interrupt. */
+ intsrc = PCI230_INT_ZCLK_CT1;
+ } else {
+ /* Using DAC FIFO interrupt. */
+ intsrc = PCI230P2_INT_DAC;
+ }
+ /*
+ * Disable interrupt and wait for interrupt routine to finish running
+ * unless we are called from the interrupt routine.
+ */
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ devpriv->ier &= ~intsrc;
+ while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) {
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ }
+ outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+ if (devpriv->hwver >= 2) {
+ /*
+ * Using DAC FIFO. Reset FIFO, clear underrun error,
+ * disable FIFO.
+ */
+ devpriv->daccon &= PCI230_DAC_OR_MASK;
+ outw(devpriv->daccon | PCI230P2_DAC_FIFO_RESET |
+ PCI230P2_DAC_FIFO_UNDERRUN_CLEAR,
+ devpriv->daqio + PCI230_DACCON);
+ }
+ /* Release resources. */
+ pci230_release_all_resources(dev, OWNER_AOCMD);
+}
+
+static void pci230_handle_ao_nofifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned short data;
+ int i;
+
+ if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
+ return;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (!comedi_buf_read_samples(s, &data, 1)) {
+ async->events |= COMEDI_CB_OVERFLOW;
+ return;
+ }
+ pci230_ao_write_nofifo(dev, data, chan);
+ s->readback[chan] = data;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+}
+
+/*
+ * Loads DAC FIFO (if using it) from buffer.
+ * Returns false if AO finished due to completion or error, true if still going.
+ */
+static bool pci230_handle_ao_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int num_scans = comedi_nscans_left(s, 0);
+ unsigned int room;
+ unsigned short dacstat;
+ unsigned int i, n;
+ unsigned int events = 0;
+
+ /* Get DAC FIFO status. */
+ dacstat = inw(devpriv->daqio + PCI230_DACCON);
+
+ if (cmd->stop_src == TRIG_COUNT && num_scans == 0)
+ events |= COMEDI_CB_EOA;
+
+ if (events == 0) {
+ /* Check for FIFO underrun. */
+ if (dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) {
+ dev_err(dev->class_dev, "AO FIFO underrun\n");
+ events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
+ }
+ /*
+ * Check for buffer underrun if FIFO less than half full
+ * (otherwise there will be loads of "DAC FIFO not half full"
+ * interrupts).
+ */
+ if (num_scans == 0 &&
+ (dacstat & PCI230P2_DAC_FIFO_HALF) == 0) {
+ dev_err(dev->class_dev, "AO buffer underrun\n");
+ events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
+ }
+ }
+ if (events == 0) {
+ /* Determine how much room is in the FIFO (in samples). */
+ if (dacstat & PCI230P2_DAC_FIFO_FULL)
+ room = PCI230P2_DAC_FIFOROOM_FULL;
+ else if (dacstat & PCI230P2_DAC_FIFO_HALF)
+ room = PCI230P2_DAC_FIFOROOM_HALFTOFULL;
+ else if (dacstat & PCI230P2_DAC_FIFO_EMPTY)
+ room = PCI230P2_DAC_FIFOROOM_EMPTY;
+ else
+ room = PCI230P2_DAC_FIFOROOM_ONETOHALF;
+ /* Convert room to number of scans that can be added. */
+ room /= cmd->chanlist_len;
+ /* Determine number of scans to process. */
+ if (num_scans > room)
+ num_scans = room;
+ /* Process scans. */
+ for (n = 0; n < num_scans; n++) {
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned short datum;
+
+ comedi_buf_read_samples(s, &datum, 1);
+ pci230_ao_write_fifo(dev, datum, chan);
+ s->readback[chan] = datum;
+ }
+ }
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ /*
+ * All data for the command has been written
+ * to FIFO. Set FIFO interrupt trigger level
+ * to 'empty'.
+ */
+ devpriv->daccon &= ~PCI230P2_DAC_INT_FIFO_MASK;
+ devpriv->daccon |= PCI230P2_DAC_INT_FIFO_EMPTY;
+ outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON);
+ }
+ /* Check if FIFO underrun occurred while writing to FIFO. */
+ dacstat = inw(devpriv->daqio + PCI230_DACCON);
+ if (dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) {
+ dev_err(dev->class_dev, "AO FIFO underrun\n");
+ events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
+ }
+ }
+ async->events |= events;
+ return !(async->events & COMEDI_CB_CANCEL_MASK);
+}
+
+static int pci230_ao_inttrig_scan_begin(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned long irqflags;
+
+ if (trig_num)
+ return -EINVAL;
+
+ spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags);
+ if (!devpriv->ao_cmd_started) {
+ spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
+ return 1;
+ }
+ /* Perform scan. */
+ if (devpriv->hwver < 2) {
+ /* Not using DAC FIFO. */
+ spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
+ pci230_handle_ao_nofifo(dev, s);
+ comedi_handle_events(dev, s);
+ } else {
+ /* Using DAC FIFO. */
+ /* Read DACSWTRIG register to trigger conversion. */
+ inw(devpriv->daqio + PCI230P2_DACSWTRIG);
+ spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
+ }
+ /* Delay. Should driver be responsible for this? */
+ /* XXX TODO: See if DAC busy bit can be used. */
+ udelay(8);
+ return 1;
+}
+
+static void pci230_ao_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long irqflags;
+
+ devpriv->ao_cmd_started = true;
+
+ if (devpriv->hwver >= 2) {
+ /* Using DAC FIFO. */
+ unsigned short scantrig;
+ bool run;
+
+ /* Preload FIFO data. */
+ run = pci230_handle_ao_fifo(dev, s);
+ comedi_handle_events(dev, s);
+ if (!run) {
+ /* Stopped. */
+ return;
+ }
+ /* Set scan trigger source. */
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ scantrig = PCI230P2_DAC_TRIG_Z2CT1;
+ break;
+ case TRIG_EXT:
+ /* Trigger on EXTTRIG/EXTCONVCLK pin. */
+ if ((cmd->scan_begin_arg & CR_INVERT) == 0) {
+ /* +ve edge */
+ scantrig = PCI230P2_DAC_TRIG_EXTP;
+ } else {
+ /* -ve edge */
+ scantrig = PCI230P2_DAC_TRIG_EXTN;
+ }
+ break;
+ case TRIG_INT:
+ scantrig = PCI230P2_DAC_TRIG_SW;
+ break;
+ default:
+ /* Shouldn't get here. */
+ scantrig = PCI230P2_DAC_TRIG_NONE;
+ break;
+ }
+ devpriv->daccon =
+ (devpriv->daccon & ~PCI230P2_DAC_TRIG_MASK) | scantrig;
+ outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON);
+ }
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ if (devpriv->hwver < 2) {
+ /* Not using DAC FIFO. */
+ /* Enable CT1 timer interrupt. */
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ devpriv->ier |= PCI230_INT_ZCLK_CT1;
+ outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
+ spin_unlock_irqrestore(&devpriv->isr_spinlock,
+ irqflags);
+ }
+ /* Set CT1 gate high to start counting. */
+ outb(GAT_CONFIG(1, GAT_VCC), dev->iobase + PCI230_ZGAT_SCE);
+ break;
+ case TRIG_INT:
+ async->inttrig = pci230_ao_inttrig_scan_begin;
+ break;
+ }
+ if (devpriv->hwver >= 2) {
+ /* Using DAC FIFO. Enable DAC FIFO interrupt. */
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ devpriv->ier |= PCI230P2_INT_DAC;
+ outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+ }
+}
+
+static int pci230_ao_inttrig_start(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_src)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+ pci230_ao_start(dev, s);
+
+ return 1;
+}
+
+static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned short daccon;
+ unsigned int range;
+
+ /* Get the command. */
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* Claim Z2-CT1. */
+ if (!pci230_claim_shared(dev, RES_Z2CT1, OWNER_AOCMD))
+ return -EBUSY;
+ }
+
+ /*
+ * Set range - see analogue output range table; 0 => unipolar 10V,
+ * 1 => bipolar +/-10V range scale
+ */
+ range = CR_RANGE(cmd->chanlist[0]);
+ devpriv->ao_bipolar = comedi_range_is_bipolar(s, range);
+ daccon = devpriv->ao_bipolar ? PCI230_DAC_OR_BIP : PCI230_DAC_OR_UNI;
+ /* Use DAC FIFO for hardware version 2 onwards. */
+ if (devpriv->hwver >= 2) {
+ unsigned short dacen;
+ unsigned int i;
+
+ dacen = 0;
+ for (i = 0; i < cmd->chanlist_len; i++)
+ dacen |= 1 << CR_CHAN(cmd->chanlist[i]);
+
+ /* Set channel scan list. */
+ outw(dacen, devpriv->daqio + PCI230P2_DACEN);
+ /*
+ * Enable DAC FIFO.
+ * Set DAC scan source to 'none'.
+ * Set DAC FIFO interrupt trigger level to 'not half full'.
+ * Reset DAC FIFO and clear underrun.
+ *
+ * N.B. DAC FIFO interrupts are currently disabled.
+ */
+ daccon |= PCI230P2_DAC_FIFO_EN | PCI230P2_DAC_FIFO_RESET |
+ PCI230P2_DAC_FIFO_UNDERRUN_CLEAR |
+ PCI230P2_DAC_TRIG_NONE | PCI230P2_DAC_INT_FIFO_NHALF;
+ }
+
+ /* Set DACCON. */
+ outw(daccon, devpriv->daqio + PCI230_DACCON);
+ /* Preserve most of DACCON apart from write-only, transient bits. */
+ devpriv->daccon = daccon & ~(PCI230P2_DAC_FIFO_RESET |
+ PCI230P2_DAC_FIFO_UNDERRUN_CLEAR);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /*
+ * Set the counter timer 1 to the specified scan frequency.
+ * cmd->scan_begin_arg is sampling period in ns.
+ * Gate it off for now.
+ */
+ outb(GAT_CONFIG(1, GAT_GND), dev->iobase + PCI230_ZGAT_SCE);
+ pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3,
+ cmd->scan_begin_arg,
+ cmd->flags);
+ }
+
+ /* N.B. cmd->start_src == TRIG_INT */
+ s->async->inttrig = pci230_ao_inttrig_start;
+
+ return 0;
+}
+
+static int pci230_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ pci230_ao_stop(dev, s);
+ return 0;
+}
+
+static int pci230_ai_check_scan_period(struct comedi_cmd *cmd)
+{
+ unsigned int min_scan_period, chanlist_len;
+ int err = 0;
+
+ chanlist_len = cmd->chanlist_len;
+ if (cmd->chanlist_len == 0)
+ chanlist_len = 1;
+
+ min_scan_period = chanlist_len * cmd->convert_arg;
+ if (min_scan_period < chanlist_len ||
+ min_scan_period < cmd->convert_arg) {
+ /* Arithmetic overflow. */
+ min_scan_period = UINT_MAX;
+ err++;
+ }
+ if (cmd->scan_begin_arg < min_scan_period) {
+ cmd->scan_begin_arg = min_scan_period;
+ err++;
+ }
+
+ return !err;
+}
+
+static int pci230_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int max_diff_chan = (s->n_chan / 2) - 1;
+ unsigned int prev_chan = 0;
+ unsigned int prev_range = 0;
+ unsigned int prev_aref = 0;
+ bool prev_bipolar = false;
+ unsigned int subseq_len = 0;
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chanspec = cmd->chanlist[i];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ bool bipolar = comedi_range_is_bipolar(s, range);
+
+ if (aref == AREF_DIFF && chan >= max_diff_chan) {
+ dev_dbg(dev->class_dev,
+ "%s: differential channel number out of range 0 to %u\n",
+ __func__, max_diff_chan);
+ return -EINVAL;
+ }
+
+ if (i > 0) {
+ /*
+ * Channel numbers must strictly increase or
+ * subsequence must repeat exactly.
+ */
+ if (chan <= prev_chan && subseq_len == 0)
+ subseq_len = i;
+
+ if (subseq_len > 0 &&
+ cmd->chanlist[i % subseq_len] != chanspec) {
+ dev_dbg(dev->class_dev,
+ "%s: channel numbers must increase or sequence must repeat exactly\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (aref != prev_aref) {
+ dev_dbg(dev->class_dev,
+ "%s: channel sequence analogue references must be all the same (single-ended or differential)\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (bipolar != prev_bipolar) {
+ dev_dbg(dev->class_dev,
+ "%s: channel sequence ranges must be all bipolar or all unipolar\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (aref != AREF_DIFF && range != prev_range &&
+ ((chan ^ prev_chan) & ~1) == 0) {
+ dev_dbg(dev->class_dev,
+ "%s: single-ended channel pairs must have the same range\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+ prev_chan = chan;
+ prev_range = range;
+ prev_aref = aref;
+ prev_bipolar = bipolar;
+ }
+
+ if (subseq_len == 0)
+ subseq_len = cmd->chanlist_len;
+
+ if (cmd->chanlist_len % subseq_len) {
+ dev_dbg(dev->class_dev,
+ "%s: sequence must repeat exactly\n", __func__);
+ return -EINVAL;
+ }
+
+ /*
+ * Buggy PCI230+ or PCI260+ requires channel 0 to be (first) in the
+ * sequence if the sequence contains more than one channel. Hardware
+ * versions 1 and 2 have the bug. There is no hardware version 3.
+ *
+ * Actually, there are two firmwares that report themselves as
+ * hardware version 1 (the boards have different ADC chips with
+ * slightly different timing requirements, which was supposed to
+ * be invisible to software). The first one doesn't seem to have
+ * the bug, but the second one does, and we can't tell them apart!
+ */
+ if (devpriv->hwver > 0 && devpriv->hwver < 4) {
+ if (subseq_len > 1 && CR_CHAN(cmd->chanlist[0])) {
+ dev_info(dev->class_dev,
+ "amplc_pci230: ai_cmdtest: Buggy PCI230+/260+ h/w version %u requires first channel of multi-channel sequence to be 0 (corrected in h/w version 4)\n",
+ devpriv->hwver);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int pci230_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct pci230_board *thisboard = dev->board_ptr;
+ struct pci230_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+
+ tmp = TRIG_FOLLOW | TRIG_TIMER | TRIG_INT;
+ if (thisboard->have_dio || thisboard->min_hwver > 0) {
+ /*
+ * Unfortunately, we cannot trigger a scan off an external
+ * source on the PCI260 board, since it uses the PPIC0 (DIO)
+ * input, which isn't present on the PCI260. For PCI260+
+ * we can use the EXTTRIG/EXTCONVCLK input on pin 17 instead.
+ */
+ tmp |= TRIG_EXT;
+ }
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, tmp);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_INT | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /*
+ * If scan_begin_src is not TRIG_FOLLOW, then a monostable will be
+ * set up to generate a fixed number of timed conversion pulses.
+ */
+ if (cmd->scan_begin_src != TRIG_FOLLOW &&
+ cmd->convert_src != TRIG_TIMER)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SPEED_AI_SE 3200 /* PCI230 SE: 3200 ns => 312.5 kHz */
+#define MAX_SPEED_AI_DIFF 8000 /* PCI230 DIFF: 8000 ns => 125 kHz */
+#define MAX_SPEED_AI_PLUS 4000 /* PCI230+: 4000 ns => 250 kHz */
+/*
+ * Comedi limit due to unsigned int cmd. Driver limit =
+ * 2^16 (16bit * counter) * 1000000ns (1kHz onboard clock) = 65.536s
+ */
+#define MIN_SPEED_AI 4294967295u /* 4294967295ns = 4.29s */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ unsigned int max_speed_ai;
+
+ if (devpriv->hwver == 0) {
+ /*
+ * PCI230 or PCI260. Max speed depends whether
+ * single-ended or pseudo-differential.
+ */
+ if (cmd->chanlist && cmd->chanlist_len > 0) {
+ /* Peek analogue reference of first channel. */
+ if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF)
+ max_speed_ai = MAX_SPEED_AI_DIFF;
+ else
+ max_speed_ai = MAX_SPEED_AI_SE;
+
+ } else {
+ /* No channel list. Assume single-ended. */
+ max_speed_ai = MAX_SPEED_AI_SE;
+ }
+ } else {
+ /* PCI230+ or PCI260+. */
+ max_speed_ai = MAX_SPEED_AI_PLUS;
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ max_speed_ai);
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg,
+ MIN_SPEED_AI);
+ } else if (cmd->convert_src == TRIG_EXT) {
+ /*
+ * external trigger
+ *
+ * convert_arg == (CR_EDGE | 0)
+ * => trigger on +ve edge.
+ * convert_arg == (CR_EDGE | CR_INVERT | 0)
+ * => trigger on -ve edge.
+ */
+ if (cmd->convert_arg & CR_FLAGS_MASK) {
+ /* Trigger number must be 0. */
+ if (cmd->convert_arg & ~CR_FLAGS_MASK) {
+ cmd->convert_arg = COMBINE(cmd->convert_arg, 0,
+ ~CR_FLAGS_MASK);
+ err |= -EINVAL;
+ }
+ /*
+ * The only flags allowed are CR_INVERT and CR_EDGE.
+ * CR_EDGE is required.
+ */
+ if ((cmd->convert_arg & CR_FLAGS_MASK & ~CR_INVERT) !=
+ CR_EDGE) {
+ /* Set CR_EDGE, preserve CR_INVERT. */
+ cmd->convert_arg =
+ COMBINE(cmd->start_arg, CR_EDGE | 0,
+ CR_FLAGS_MASK & ~CR_INVERT);
+ err |= -EINVAL;
+ }
+ } else {
+ /*
+ * Backwards compatibility with previous versions:
+ * convert_arg == 0 => trigger on -ve edge.
+ * convert_arg == 1 => trigger on +ve edge.
+ */
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg,
+ 1);
+ }
+ } else {
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_EXT) {
+ /*
+ * external "trigger" to begin each scan:
+ * scan_begin_arg==0 => use PPC0 input -> gate of CT0 -> gate
+ * of CT2 (sample convert trigger is CT2)
+ */
+ if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) {
+ cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
+ ~CR_FLAGS_MASK);
+ err |= -EINVAL;
+ }
+ /* The only flag allowed is CR_EDGE, which is ignored. */
+ if (cmd->scan_begin_arg & CR_FLAGS_MASK & ~CR_EDGE) {
+ cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
+ CR_FLAGS_MASK & ~CR_EDGE);
+ err |= -EINVAL;
+ }
+ } else if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* N.B. cmd->convert_arg is also TRIG_TIMER */
+ if (!pci230_ai_check_scan_period(cmd))
+ err |= -EINVAL;
+
+ } else {
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ tmp = cmd->convert_arg;
+ pci230_ns_to_single_timer(&cmd->convert_arg, cmd->flags);
+ if (tmp != cmd->convert_arg)
+ err++;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* N.B. cmd->convert_arg is also TRIG_TIMER */
+ tmp = cmd->scan_begin_arg;
+ pci230_ns_to_single_timer(&cmd->scan_begin_arg, cmd->flags);
+ if (!pci230_ai_check_scan_period(cmd)) {
+ /* Was below minimum required. Round up. */
+ pci230_ns_to_single_timer(&cmd->scan_begin_arg,
+ CMDF_ROUND_UP);
+ pci230_ai_check_scan_period(cmd);
+ }
+ if (tmp != cmd->scan_begin_arg)
+ err++;
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= pci230_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void pci230_ai_update_fifo_trigger_level(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int wake;
+ unsigned short triglev;
+ unsigned short adccon;
+
+ if (cmd->flags & CMDF_WAKE_EOS)
+ wake = cmd->scan_end_arg - s->async->cur_chan;
+ else
+ wake = comedi_nsamples_left(s, PCI230_ADC_FIFOLEVEL_HALFFULL);
+
+ if (wake >= PCI230_ADC_FIFOLEVEL_HALFFULL) {
+ triglev = PCI230_ADC_INT_FIFO_HALF;
+ } else if (wake > 1 && devpriv->hwver > 0) {
+ /* PCI230+/260+ programmable FIFO interrupt level. */
+ if (devpriv->adcfifothresh != wake) {
+ devpriv->adcfifothresh = wake;
+ outw(wake, devpriv->daqio + PCI230P_ADCFFTH);
+ }
+ triglev = PCI230P_ADC_INT_FIFO_THRESH;
+ } else {
+ triglev = PCI230_ADC_INT_FIFO_NEMPTY;
+ }
+ adccon = (devpriv->adccon & ~PCI230_ADC_INT_FIFO_MASK) | triglev;
+ if (adccon != devpriv->adccon) {
+ devpriv->adccon = adccon;
+ outw(adccon, devpriv->daqio + PCI230_ADCCON);
+ }
+}
+
+static int pci230_ai_inttrig_convert(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned long irqflags;
+ unsigned int delayus;
+
+ if (trig_num)
+ return -EINVAL;
+
+ spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags);
+ if (!devpriv->ai_cmd_started) {
+ spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
+ return 1;
+ }
+ /*
+ * Trigger conversion by toggling Z2-CT2 output.
+ * Finish with output high.
+ */
+ comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0);
+ comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1);
+ /*
+ * Delay. Should driver be responsible for this? An
+ * alternative would be to wait until conversion is complete,
+ * but we can't tell when it's complete because the ADC busy
+ * bit has a different meaning when FIFO enabled (and when
+ * FIFO not enabled, it only works for software triggers).
+ */
+ if ((devpriv->adccon & PCI230_ADC_IM_MASK) == PCI230_ADC_IM_DIF &&
+ devpriv->hwver == 0) {
+ /* PCI230/260 in differential mode */
+ delayus = 8;
+ } else {
+ /* single-ended or PCI230+/260+ */
+ delayus = 4;
+ }
+ spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
+ udelay(delayus);
+ return 1;
+}
+
+static int pci230_ai_inttrig_scan_begin(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned long irqflags;
+ unsigned char zgat;
+
+ if (trig_num)
+ return -EINVAL;
+
+ spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags);
+ if (devpriv->ai_cmd_started) {
+ /* Trigger scan by waggling CT0 gate source. */
+ zgat = GAT_CONFIG(0, GAT_GND);
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ zgat = GAT_CONFIG(0, GAT_VCC);
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ }
+ spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
+
+ return 1;
+}
+
+static void pci230_ai_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned long irqflags;
+ struct comedi_cmd *cmd;
+ bool started;
+
+ spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags);
+ started = devpriv->ai_cmd_started;
+ devpriv->ai_cmd_started = false;
+ spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
+ if (!started)
+ return;
+ cmd = &s->async->cmd;
+ if (cmd->convert_src == TRIG_TIMER) {
+ /* Stop conversion rate generator. */
+ pci230_cancel_ct(dev, 2);
+ }
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ /* Stop scan period monostable. */
+ pci230_cancel_ct(dev, 0);
+ }
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ /*
+ * Disable ADC interrupt and wait for interrupt routine to finish
+ * running unless we are called from the interrupt routine.
+ */
+ devpriv->ier &= ~PCI230_INT_ADC;
+ while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) {
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ }
+ outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+ /*
+ * Reset FIFO, disable FIFO and set start conversion source to none.
+ * Keep se/diff and bip/uni settings.
+ */
+ devpriv->adccon =
+ (devpriv->adccon & (PCI230_ADC_IR_MASK | PCI230_ADC_IM_MASK)) |
+ PCI230_ADC_TRIG_NONE;
+ outw(devpriv->adccon | PCI230_ADC_FIFO_RESET,
+ devpriv->daqio + PCI230_ADCCON);
+ /* Release resources. */
+ pci230_release_all_resources(dev, OWNER_AICMD);
+}
+
+static void pci230_ai_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned long irqflags;
+ unsigned short conv;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+
+ devpriv->ai_cmd_started = true;
+
+ /* Enable ADC FIFO trigger level interrupt. */
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ devpriv->ier |= PCI230_INT_ADC;
+ outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+
+ /*
+ * Update conversion trigger source which is currently set
+ * to CT2 output, which is currently stuck high.
+ */
+ switch (cmd->convert_src) {
+ default:
+ conv = PCI230_ADC_TRIG_NONE;
+ break;
+ case TRIG_TIMER:
+ /* Using CT2 output. */
+ conv = PCI230_ADC_TRIG_Z2CT2;
+ break;
+ case TRIG_EXT:
+ if (cmd->convert_arg & CR_EDGE) {
+ if ((cmd->convert_arg & CR_INVERT) == 0) {
+ /* Trigger on +ve edge. */
+ conv = PCI230_ADC_TRIG_EXTP;
+ } else {
+ /* Trigger on -ve edge. */
+ conv = PCI230_ADC_TRIG_EXTN;
+ }
+ } else {
+ /* Backwards compatibility. */
+ if (cmd->convert_arg) {
+ /* Trigger on +ve edge. */
+ conv = PCI230_ADC_TRIG_EXTP;
+ } else {
+ /* Trigger on -ve edge. */
+ conv = PCI230_ADC_TRIG_EXTN;
+ }
+ }
+ break;
+ case TRIG_INT:
+ /*
+ * Use CT2 output for software trigger due to problems
+ * in differential mode on PCI230/260.
+ */
+ conv = PCI230_ADC_TRIG_Z2CT2;
+ break;
+ }
+ devpriv->adccon = (devpriv->adccon & ~PCI230_ADC_TRIG_MASK) | conv;
+ outw(devpriv->adccon, devpriv->daqio + PCI230_ADCCON);
+ if (cmd->convert_src == TRIG_INT)
+ async->inttrig = pci230_ai_inttrig_convert;
+
+ /*
+ * Update FIFO interrupt trigger level, which is currently
+ * set to "full".
+ */
+ pci230_ai_update_fifo_trigger_level(dev, s);
+ if (cmd->convert_src == TRIG_TIMER) {
+ /* Update timer gates. */
+ unsigned char zgat;
+
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ /*
+ * Conversion timer CT2 needs to be gated by
+ * inverted output of monostable CT2.
+ */
+ zgat = GAT_CONFIG(2, GAT_NOUTNM2);
+ } else {
+ /*
+ * Conversion timer CT2 needs to be gated on
+ * continuously.
+ */
+ zgat = GAT_CONFIG(2, GAT_VCC);
+ }
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ /* Set monostable CT0 trigger source. */
+ switch (cmd->scan_begin_src) {
+ default:
+ zgat = GAT_CONFIG(0, GAT_VCC);
+ break;
+ case TRIG_EXT:
+ /*
+ * For CT0 on PCI230, the external trigger
+ * (gate) signal comes from PPC0, which is
+ * channel 16 of the DIO subdevice. The
+ * application needs to configure this as an
+ * input in order to use it as an external scan
+ * trigger.
+ */
+ zgat = GAT_CONFIG(0, GAT_EXT);
+ break;
+ case TRIG_TIMER:
+ /*
+ * Monostable CT0 triggered by rising edge on
+ * inverted output of CT1 (falling edge on CT1).
+ */
+ zgat = GAT_CONFIG(0, GAT_NOUTNM2);
+ break;
+ case TRIG_INT:
+ /*
+ * Monostable CT0 is triggered by inttrig
+ * function waggling the CT0 gate source.
+ */
+ zgat = GAT_CONFIG(0, GAT_VCC);
+ break;
+ }
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ /*
+ * Scan period timer CT1 needs to be
+ * gated on to start counting.
+ */
+ zgat = GAT_CONFIG(1, GAT_VCC);
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ break;
+ case TRIG_INT:
+ async->inttrig = pci230_ai_inttrig_scan_begin;
+ break;
+ }
+ }
+ } else if (cmd->convert_src != TRIG_INT) {
+ /* No longer need Z2-CT2. */
+ pci230_release_shared(dev, RES_Z2CT2, OWNER_AICMD);
+ }
+}
+
+static int pci230_ai_inttrig_start(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+ pci230_ai_start(dev, s);
+
+ return 1;
+}
+
+static void pci230_handle_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int status_fifo;
+ unsigned int i;
+ unsigned int todo;
+ unsigned int fifoamount;
+ unsigned short val;
+
+ /* Determine number of samples to read. */
+ todo = comedi_nsamples_left(s, PCI230_ADC_FIFOLEVEL_HALFFULL);
+ if (todo == 0)
+ return;
+
+ fifoamount = 0;
+ for (i = 0; i < todo; i++) {
+ if (fifoamount == 0) {
+ /* Read FIFO state. */
+ status_fifo = inw(devpriv->daqio + PCI230_ADCCON);
+ if (status_fifo & PCI230_ADC_FIFO_FULL_LATCHED) {
+ /*
+ * Report error otherwise FIFO overruns will go
+ * unnoticed by the caller.
+ */
+ dev_err(dev->class_dev, "AI FIFO overrun\n");
+ async->events |= COMEDI_CB_ERROR;
+ break;
+ } else if (status_fifo & PCI230_ADC_FIFO_EMPTY) {
+ /* FIFO empty. */
+ break;
+ } else if (status_fifo & PCI230_ADC_FIFO_HALF) {
+ /* FIFO half full. */
+ fifoamount = PCI230_ADC_FIFOLEVEL_HALFFULL;
+ } else if (devpriv->hwver > 0) {
+ /* Read PCI230+/260+ ADC FIFO level. */
+ fifoamount = inw(devpriv->daqio +
+ PCI230P_ADCFFLEV);
+ if (fifoamount == 0)
+ break; /* Shouldn't happen. */
+ } else {
+ /* FIFO not empty. */
+ fifoamount = 1;
+ }
+ }
+
+ val = pci230_ai_read(dev);
+ if (!comedi_buf_write_samples(s, &val, 1))
+ break;
+
+ fifoamount--;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+
+ /* update FIFO interrupt trigger level if still running */
+ if (!(async->events & COMEDI_CB_CANCEL_MASK))
+ pci230_ai_update_fifo_trigger_level(dev, s);
+}
+
+static int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int i, chan, range, diff;
+ unsigned int res_mask;
+ unsigned short adccon, adcen;
+ unsigned char zgat;
+
+ /* Get the command. */
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+
+ /*
+ * Determine which shared resources are needed.
+ */
+ res_mask = 0;
+ /*
+ * Need Z2-CT2 to supply a conversion trigger source at a high
+ * logic level, even if not doing timed conversions.
+ */
+ res_mask |= RES_Z2CT2;
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ /* Using Z2-CT0 monostable to gate Z2-CT2 conversion timer */
+ res_mask |= RES_Z2CT0;
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* Using Z2-CT1 for scan frequency */
+ res_mask |= RES_Z2CT1;
+ }
+ }
+ /* Claim resources. */
+ if (!pci230_claim_shared(dev, res_mask, OWNER_AICMD))
+ return -EBUSY;
+
+ /*
+ * Steps:
+ * - Set channel scan list.
+ * - Set channel gains.
+ * - Enable and reset FIFO, specify uni/bip, se/diff, and set
+ * start conversion source to point to something at a high logic
+ * level (we use the output of counter/timer 2 for this purpose.
+ * - PAUSE to allow things to settle down.
+ * - Reset the FIFO again because it needs resetting twice and there
+ * may have been a false conversion trigger on some versions of
+ * PCI230/260 due to the start conversion source being set to a
+ * high logic level.
+ * - Enable ADC FIFO level interrupt.
+ * - Set actual conversion trigger source and FIFO interrupt trigger
+ * level.
+ * - If convert_src is TRIG_TIMER, set up the timers.
+ */
+
+ adccon = PCI230_ADC_FIFO_EN;
+ adcen = 0;
+
+ if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF) {
+ /* Differential - all channels must be differential. */
+ diff = 1;
+ adccon |= PCI230_ADC_IM_DIF;
+ } else {
+ /* Single ended - all channels must be single-ended. */
+ diff = 0;
+ adccon |= PCI230_ADC_IM_SE;
+ }
+
+ range = CR_RANGE(cmd->chanlist[0]);
+ devpriv->ai_bipolar = comedi_range_is_bipolar(s, range);
+ if (devpriv->ai_bipolar)
+ adccon |= PCI230_ADC_IR_BIP;
+ else
+ adccon |= PCI230_ADC_IR_UNI;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int gainshift;
+
+ chan = CR_CHAN(cmd->chanlist[i]);
+ range = CR_RANGE(cmd->chanlist[i]);
+ if (diff) {
+ gainshift = 2 * chan;
+ if (devpriv->hwver == 0) {
+ /*
+ * Original PCI230/260 expects both inputs of
+ * the differential channel to be enabled.
+ */
+ adcen |= 3 << gainshift;
+ } else {
+ /*
+ * PCI230+/260+ expects only one input of the
+ * differential channel to be enabled.
+ */
+ adcen |= 1 << gainshift;
+ }
+ } else {
+ gainshift = chan & ~1;
+ adcen |= 1 << chan;
+ }
+ devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) |
+ (pci230_ai_gain[range] << gainshift);
+ }
+
+ /* Set channel scan list. */
+ outw(adcen, devpriv->daqio + PCI230_ADCEN);
+
+ /* Set channel gains. */
+ outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG);
+
+ /*
+ * Set counter/timer 2 output high for use as the initial start
+ * conversion source.
+ */
+ comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1);
+
+ /*
+ * Temporarily use CT2 output as conversion trigger source and
+ * temporarily set FIFO interrupt trigger level to 'full'.
+ */
+ adccon |= PCI230_ADC_INT_FIFO_FULL | PCI230_ADC_TRIG_Z2CT2;
+
+ /*
+ * Enable and reset FIFO, specify FIFO trigger level full, specify
+ * uni/bip, se/diff, and temporarily set the start conversion source
+ * to CT2 output. Note that CT2 output is currently high, and this
+ * will produce a false conversion trigger on some versions of the
+ * PCI230/260, but that will be dealt with later.
+ */
+ devpriv->adccon = adccon;
+ outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON);
+
+ /*
+ * Delay -
+ * Failure to include this will result in the first few channels'-worth
+ * of data being corrupt, normally manifesting itself by large negative
+ * voltages. It seems the board needs time to settle between the first
+ * FIFO reset (above) and the second FIFO reset (below). Setting the
+ * channel gains and scan list _before_ the first FIFO reset also
+ * helps, though only slightly.
+ */
+ usleep_range(25, 100);
+
+ /* Reset FIFO again. */
+ outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ /*
+ * Set up CT2 as conversion timer, but gate it off for now.
+ * Note, counter/timer output 2 can be monitored on the
+ * connector: PCI230 pin 21, PCI260 pin 18.
+ */
+ zgat = GAT_CONFIG(2, GAT_GND);
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ /* Set counter/timer 2 to the specified conversion period. */
+ pci230_ct_setup_ns_mode(dev, 2, I8254_MODE3, cmd->convert_arg,
+ cmd->flags);
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ /*
+ * Set up monostable on CT0 output for scan timing. A
+ * rising edge on the trigger (gate) input of CT0 will
+ * trigger the monostable, causing its output to go low
+ * for the configured period. The period depends on
+ * the conversion period and the number of conversions
+ * in the scan.
+ *
+ * Set the trigger high before setting up the
+ * monostable to stop it triggering. The trigger
+ * source will be changed later.
+ */
+ zgat = GAT_CONFIG(0, GAT_VCC);
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ pci230_ct_setup_ns_mode(dev, 0, I8254_MODE1,
+ ((uint64_t)cmd->convert_arg *
+ cmd->scan_end_arg),
+ CMDF_ROUND_UP);
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /*
+ * Monostable on CT0 will be triggered by
+ * output of CT1 at configured scan frequency.
+ *
+ * Set up CT1 but gate it off for now.
+ */
+ zgat = GAT_CONFIG(1, GAT_GND);
+ outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
+ pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3,
+ cmd->scan_begin_arg,
+ cmd->flags);
+ }
+ }
+ }
+
+ if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = pci230_ai_inttrig_start;
+ else /* TRIG_NOW */
+ pci230_ai_start(dev, s);
+
+ return 0;
+}
+
+static int pci230_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ pci230_ai_stop(dev, s);
+ return 0;
+}
+
+/* Interrupt handler */
+static irqreturn_t pci230_interrupt(int irq, void *d)
+{
+ unsigned char status_int, valid_status_int, temp_ier;
+ struct comedi_device *dev = (struct comedi_device *)d;
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_subdevice *s_ao = dev->write_subdev;
+ struct comedi_subdevice *s_ai = dev->read_subdev;
+ unsigned long irqflags;
+
+ /* Read interrupt status/enable register. */
+ status_int = inb(dev->iobase + PCI230_INT_STAT);
+
+ if (status_int == PCI230_INT_DISABLE)
+ return IRQ_NONE;
+
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ valid_status_int = devpriv->ier & status_int;
+ /*
+ * Disable triggered interrupts.
+ * (Only those interrupts that need re-enabling, are, later in the
+ * handler).
+ */
+ temp_ier = devpriv->ier & ~status_int;
+ outb(temp_ier, dev->iobase + PCI230_INT_SCE);
+ devpriv->intr_running = true;
+ devpriv->intr_cpuid = THISCPU;
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+
+ /*
+ * Check the source of interrupt and handle it.
+ * The PCI230 can cope with concurrent ADC, DAC, PPI C0 and C3
+ * interrupts. However, at present (Comedi-0.7.60) does not allow
+ * concurrent execution of commands, instructions or a mixture of the
+ * two.
+ */
+
+ if (valid_status_int & PCI230_INT_ZCLK_CT1)
+ pci230_handle_ao_nofifo(dev, s_ao);
+
+ if (valid_status_int & PCI230P2_INT_DAC)
+ pci230_handle_ao_fifo(dev, s_ao);
+
+ if (valid_status_int & PCI230_INT_ADC)
+ pci230_handle_ai(dev, s_ai);
+
+ /* Reenable interrupts. */
+ spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
+ if (devpriv->ier != temp_ier)
+ outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
+ devpriv->intr_running = false;
+ spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
+
+ comedi_handle_events(dev, s_ao);
+ comedi_handle_events(dev, s_ai);
+
+ return IRQ_HANDLED;
+}
+
+/* Check if PCI device matches a specific board. */
+static bool pci230_match_pci_board(const struct pci230_board *board,
+ struct pci_dev *pci_dev)
+{
+ /* assume pci_dev->device != PCI_DEVICE_ID_INVALID */
+ if (board->id != pci_dev->device)
+ return false;
+ if (board->min_hwver == 0)
+ return true;
+ /* Looking for a '+' model. First check length of registers. */
+ if (pci_resource_len(pci_dev, 3) < 32)
+ return false; /* Not a '+' model. */
+ /*
+ * TODO: temporarily enable PCI device and read the hardware version
+ * register. For now, assume it's okay.
+ */
+ return true;
+}
+
+/* Look for board matching PCI device. */
+static const struct pci230_board *pci230_find_pci_board(struct pci_dev *pci_dev)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pci230_boards); i++)
+ if (pci230_match_pci_board(&pci230_boards[i], pci_dev))
+ return &pci230_boards[i];
+ return NULL;
+}
+
+static int pci230_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ const struct pci230_board *thisboard;
+ struct pci230_private *devpriv;
+ struct comedi_subdevice *s;
+ int rc;
+
+ dev_info(dev->class_dev, "amplc_pci230: attach pci %s\n",
+ pci_name(pci_dev));
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->isr_spinlock);
+ spin_lock_init(&devpriv->res_spinlock);
+ spin_lock_init(&devpriv->ai_stop_spinlock);
+ spin_lock_init(&devpriv->ao_stop_spinlock);
+
+ dev->board_ptr = pci230_find_pci_board(pci_dev);
+ if (!dev->board_ptr) {
+ dev_err(dev->class_dev,
+ "amplc_pci230: BUG! cannot determine board type!\n");
+ return -EINVAL;
+ }
+ thisboard = dev->board_ptr;
+ dev->board_name = thisboard->name;
+
+ rc = comedi_pci_enable(dev);
+ if (rc)
+ return rc;
+
+ /*
+ * Read base addresses of the PCI230's two I/O regions from PCI
+ * configuration register.
+ */
+ dev->iobase = pci_resource_start(pci_dev, 2);
+ devpriv->daqio = pci_resource_start(pci_dev, 3);
+ dev_dbg(dev->class_dev,
+ "%s I/O region 1 0x%04lx I/O region 2 0x%04lx\n",
+ dev->board_name, dev->iobase, devpriv->daqio);
+ /* Read bits of DACCON register - only the output range. */
+ devpriv->daccon = inw(devpriv->daqio + PCI230_DACCON) &
+ PCI230_DAC_OR_MASK;
+ /*
+ * Read hardware version register and set extended function register
+ * if they exist.
+ */
+ if (pci_resource_len(pci_dev, 3) >= 32) {
+ unsigned short extfunc = 0;
+
+ devpriv->hwver = inw(devpriv->daqio + PCI230P_HWVER);
+ if (devpriv->hwver < thisboard->min_hwver) {
+ dev_err(dev->class_dev,
+ "%s - bad hardware version - got %u, need %u\n",
+ dev->board_name, devpriv->hwver,
+ thisboard->min_hwver);
+ return -EIO;
+ }
+ if (devpriv->hwver > 0) {
+ if (!thisboard->have_dio) {
+ /*
+ * No DIO ports. Route counters' external gates
+ * to the EXTTRIG signal (PCI260+ pin 17).
+ * (Otherwise, they would be routed to DIO
+ * inputs PC0, PC1 and PC2 which don't exist
+ * on PCI260[+].)
+ */
+ extfunc |= PCI230P_EXTFUNC_GAT_EXTTRIG;
+ }
+ if (thisboard->ao_bits && devpriv->hwver >= 2) {
+ /* Enable DAC FIFO functionality. */
+ extfunc |= PCI230P2_EXTFUNC_DACFIFO;
+ }
+ }
+ outw(extfunc, devpriv->daqio + PCI230P_EXTFUNC);
+ if (extfunc & PCI230P2_EXTFUNC_DACFIFO) {
+ /*
+ * Temporarily enable DAC FIFO, reset it and disable
+ * FIFO wraparound.
+ */
+ outw(devpriv->daccon | PCI230P2_DAC_FIFO_EN |
+ PCI230P2_DAC_FIFO_RESET,
+ devpriv->daqio + PCI230_DACCON);
+ /* Clear DAC FIFO channel enable register. */
+ outw(0, devpriv->daqio + PCI230P2_DACEN);
+ /* Disable DAC FIFO. */
+ outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON);
+ }
+ }
+ /* Disable board's interrupts. */
+ outb(0, dev->iobase + PCI230_INT_SCE);
+ /* Set ADC to a reasonable state. */
+ devpriv->adcg = 0;
+ devpriv->adccon = PCI230_ADC_TRIG_NONE | PCI230_ADC_IM_SE |
+ PCI230_ADC_IR_BIP;
+ outw(1 << 0, devpriv->daqio + PCI230_ADCEN);
+ outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG);
+ outw(devpriv->adccon | PCI230_ADC_FIFO_RESET,
+ devpriv->daqio + PCI230_ADCCON);
+
+ if (pci_dev->irq) {
+ rc = request_irq(pci_dev->irq, pci230_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (rc == 0)
+ dev->irq = pci_dev->irq;
+ }
+
+ dev->pacer = comedi_8254_init(dev->iobase + PCI230_Z2_CT_BASE,
+ 0, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ rc = comedi_alloc_subdevices(dev, 3);
+ if (rc)
+ return rc;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND;
+ s->n_chan = 16;
+ s->maxdata = (1 << thisboard->ai_bits) - 1;
+ s->range_table = &pci230_ai_range;
+ s->insn_read = pci230_ai_insn_read;
+ s->len_chanlist = 256; /* but there are restrictions. */
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmd = pci230_ai_cmd;
+ s->do_cmdtest = pci230_ai_cmdtest;
+ s->cancel = pci230_ai_cancel;
+ }
+
+ s = &dev->subdevices[1];
+ /* analog output subdevice */
+ if (thisboard->ao_bits) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = 2;
+ s->maxdata = (1 << thisboard->ao_bits) - 1;
+ s->range_table = &pci230_ao_range;
+ s->insn_write = pci230_ao_insn_write;
+ s->len_chanlist = 2;
+ if (dev->irq) {
+ dev->write_subdev = s;
+ s->subdev_flags |= SDF_CMD_WRITE;
+ s->do_cmd = pci230_ao_cmd;
+ s->do_cmdtest = pci230_ao_cmdtest;
+ s->cancel = pci230_ao_cancel;
+ }
+
+ rc = comedi_alloc_subdev_readback(s);
+ if (rc)
+ return rc;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* digital i/o subdevice */
+ if (thisboard->have_dio) {
+ rc = subdev_8255_init(dev, s, NULL, PCI230_PPI_X_BASE);
+ if (rc)
+ return rc;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver amplc_pci230_driver = {
+ .driver_name = "amplc_pci230",
+ .module = THIS_MODULE,
+ .auto_attach = pci230_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int amplc_pci230_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pci230_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id amplc_pci230_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI230) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI260) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, amplc_pci230_pci_table);
+
+static struct pci_driver amplc_pci230_pci_driver = {
+ .name = "amplc_pci230",
+ .id_table = amplc_pci230_pci_table,
+ .probe = amplc_pci230_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci230_driver, amplc_pci230_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PCI230(+) and PCI260(+)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pci236.c b/drivers/staging/comedi/drivers/amplc_pci236.c
new file mode 100644
index 000000000..31cc38b4b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci236.c
@@ -0,0 +1,153 @@
+/*
+ * comedi/drivers/amplc_pci236.c
+ * Driver for Amplicon PCI236 DIO boards.
+ *
+ * Copyright (C) 2002-2014 MEV Ltd. <http://www.mev.co.uk/>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+/*
+ * Driver: amplc_pci236
+ * Description: Amplicon PCI236
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Amplicon] PCI236 (amplc_pci236)
+ * Updated: Fri, 25 Jul 2014 15:32:40 +0000
+ * Status: works
+ *
+ * Configuration options:
+ * none
+ *
+ * Manual configuration of PCI board (PCI236) is not supported; it is
+ * configured automatically.
+ *
+ * The PCI236 board has a single 8255 appearing as subdevice 0.
+ *
+ * Subdevice 1 pretends to be a digital input device, but it always
+ * returns 0 when read. However, if you run a command with
+ * scan_begin_src=TRIG_EXT, a rising edge on port C bit 3 acts as an
+ * external trigger, which can be used to wake up tasks. This is like
+ * the comedi_parport device. If no interrupt is connected, then
+ * subdevice 1 is unused.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "amplc_pc236.h"
+#include "plx9052.h"
+
+/* Disable, and clear, interrupts */
+#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1POL | \
+ PLX9052_INTCSR_LI2POL | \
+ PLX9052_INTCSR_LI1SEL | \
+ PLX9052_INTCSR_LI1CLRINT)
+
+/* Enable, and clear, interrupts */
+#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB | \
+ PLX9052_INTCSR_LI1POL | \
+ PLX9052_INTCSR_LI2POL | \
+ PLX9052_INTCSR_PCIENAB | \
+ PLX9052_INTCSR_LI1SEL | \
+ PLX9052_INTCSR_LI1CLRINT)
+
+static void pci236_intr_update_cb(struct comedi_device *dev, bool enable)
+{
+ struct pc236_private *devpriv = dev->private;
+
+ /* this will also clear the "local interrupt 1" latch */
+ outl(enable ? PCI236_INTR_ENABLE : PCI236_INTR_DISABLE,
+ devpriv->lcr_iobase + PLX9052_INTCSR);
+}
+
+static bool pci236_intr_chk_clr_cb(struct comedi_device *dev)
+{
+ struct pc236_private *devpriv = dev->private;
+
+ /* check if interrupt occurred */
+ if (!(inl(devpriv->lcr_iobase + PLX9052_INTCSR) &
+ PLX9052_INTCSR_LI1STAT))
+ return false;
+ /* clear the interrupt */
+ pci236_intr_update_cb(dev, devpriv->enable_irq);
+ return true;
+}
+
+static const struct pc236_board pc236_pci_board = {
+ .name = "pci236",
+ .intr_update_cb = pci236_intr_update_cb,
+ .intr_chk_clr_cb = pci236_intr_chk_clr_cb,
+};
+
+static int pci236_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ struct pc236_private *devpriv;
+ unsigned long iobase;
+ int ret;
+
+ dev_info(dev->class_dev, "amplc_pci236: attach pci %s\n",
+ pci_name(pci_dev));
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->board_ptr = &pc236_pci_board;
+ dev->board_name = pc236_pci_board.name;
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
+ iobase = pci_resource_start(pci_dev, 2);
+ return amplc_pc236_common_attach(dev, iobase, pci_dev->irq,
+ IRQF_SHARED);
+}
+
+static struct comedi_driver amplc_pci236_driver = {
+ .driver_name = "amplc_pci236",
+ .module = THIS_MODULE,
+ .auto_attach = pci236_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static const struct pci_device_id pci236_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x0009) },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, pci236_pci_table);
+
+static int amplc_pci236_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pci236_driver,
+ id->driver_data);
+}
+
+static struct pci_driver amplc_pci236_pci_driver = {
+ .name = "amplc_pci236",
+ .id_table = pci236_pci_table,
+ .probe = &amplc_pci236_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+
+module_comedi_pci_driver(amplc_pci236_driver, amplc_pci236_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PCI236 DIO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pci263.c b/drivers/staging/comedi/drivers/amplc_pci263.c
new file mode 100644
index 000000000..b6768aa90
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci263.c
@@ -0,0 +1,114 @@
+/*
+ comedi/drivers/amplc_pci263.c
+ Driver for Amplicon PCI263 relay board.
+
+ Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: amplc_pci263
+Description: Amplicon PCI263
+Author: Ian Abbott <abbotti@mev.co.uk>
+Devices: [Amplicon] PCI263 (amplc_pci263)
+Updated: Fri, 12 Apr 2013 15:19:36 +0100
+Status: works
+
+Configuration options: not applicable, uses PCI auto config
+
+The board appears as one subdevice, with 16 digital outputs, each
+connected to a reed-relay. Relay contacts are closed when output is 1.
+The state of the outputs can be read.
+*/
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+static int pci263_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase);
+ outb((s->state >> 8) & 0xff, dev->iobase + 1);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci263_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pci_dev, 2);
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* digital output subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci263_do_insn_bits;
+ /* read initial relay state */
+ s->state = inb(dev->iobase) | (inb(dev->iobase + 1) << 8);
+
+ return 0;
+}
+
+static struct comedi_driver amplc_pci263_driver = {
+ .driver_name = "amplc_pci263",
+ .module = THIS_MODULE,
+ .auto_attach = pci263_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static const struct pci_device_id pci263_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x000c) },
+ {0}
+};
+MODULE_DEVICE_TABLE(pci, pci263_pci_table);
+
+static int amplc_pci263_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pci263_driver,
+ id->driver_data);
+}
+
+static struct pci_driver amplc_pci263_pci_driver = {
+ .name = "amplc_pci263",
+ .id_table = pci263_pci_table,
+ .probe = &amplc_pci263_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci263_driver, amplc_pci263_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PCI263 relay board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/c6xdigio.c b/drivers/staging/comedi/drivers/c6xdigio.c
new file mode 100644
index 000000000..1a109e30d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/c6xdigio.c
@@ -0,0 +1,307 @@
+/*
+ * c6xdigio.c
+ * Hardware driver for Mechatronic Systems Inc. C6x_DIGIO DSP daughter card.
+ * http://web.archive.org/web/%2A/http://robot0.ge.uiuc.edu/~spong/mecha/
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1999 Dan Block
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: c6xdigio
+ * Description: Mechatronic Systems Inc. C6x_DIGIO DSP daughter card
+ * Author: Dan Block
+ * Status: unknown
+ * Devices: [Mechatronic Systems Inc.] C6x_DIGIO DSP daughter card (c6xdigio)
+ * Updated: Sun Nov 20 20:18:34 EST 2005
+ *
+ * Configuration Options:
+ * [0] - base address
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/timer.h>
+#include <linux/io.h>
+#include <linux/pnp.h>
+
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define C6XDIGIO_DATA_REG 0x00
+#define C6XDIGIO_DATA_CHAN(x) (((x) + 1) << 4)
+#define C6XDIGIO_DATA_PWM (1 << 5)
+#define C6XDIGIO_DATA_ENCODER (1 << 6)
+#define C6XDIGIO_STATUS_REG 0x01
+#define C6XDIGIO_CTRL_REG 0x02
+
+#define C6XDIGIO_TIME_OUT 20
+
+static int c6xdigio_chk_status(struct comedi_device *dev, unsigned long context)
+{
+ unsigned int status;
+ int timeout = 0;
+
+ do {
+ status = inb(dev->iobase + C6XDIGIO_STATUS_REG);
+ if ((status & 0x80) != context)
+ return 0;
+ timeout++;
+ } while (timeout < C6XDIGIO_TIME_OUT);
+
+ return -EBUSY;
+}
+
+static int c6xdigio_write_data(struct comedi_device *dev,
+ unsigned int val, unsigned int status)
+{
+ outb_p(val, dev->iobase + C6XDIGIO_DATA_REG);
+ return c6xdigio_chk_status(dev, status);
+}
+
+static int c6xdigio_get_encoder_bits(struct comedi_device *dev,
+ unsigned int *bits,
+ unsigned int cmd,
+ unsigned int status)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + C6XDIGIO_STATUS_REG);
+ val >>= 3;
+ val &= 0x07;
+
+ *bits = val;
+
+ return c6xdigio_write_data(dev, cmd, status);
+}
+
+static void c6xdigio_pwm_write(struct comedi_device *dev,
+ unsigned int chan, unsigned int val)
+{
+ unsigned int cmd = C6XDIGIO_DATA_PWM | C6XDIGIO_DATA_CHAN(chan);
+ unsigned int bits;
+
+ if (val > 498)
+ val = 498;
+ if (val < 2)
+ val = 2;
+
+ bits = (val >> 0) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
+ bits = (val >> 2) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80);
+ bits = (val >> 4) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
+ bits = (val >> 6) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80);
+ bits = (val >> 8) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
+
+ c6xdigio_write_data(dev, 0x00, 0x80);
+}
+
+static int c6xdigio_encoder_read(struct comedi_device *dev,
+ unsigned int chan)
+{
+ unsigned int cmd = C6XDIGIO_DATA_ENCODER | C6XDIGIO_DATA_CHAN(chan);
+ unsigned int val = 0;
+ unsigned int bits;
+
+ c6xdigio_write_data(dev, cmd, 0x00);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 0);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 3);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 6);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 9);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 12);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 15);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 18);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 21);
+
+ c6xdigio_write_data(dev, 0x00, 0x80);
+
+ return val;
+}
+
+static int c6xdigio_pwm_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = (s->state >> (16 * chan)) & 0xffff;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ c6xdigio_pwm_write(dev, chan, val);
+ }
+
+ /*
+ * There are only 2 PWM channels and they have a maxdata of 500.
+ * Instead of allocating private data to save the values in for
+ * readback this driver just packs the values for the two channels
+ * in the s->state.
+ */
+ s->state &= (0xffff << (16 * chan));
+ s->state |= (val << (16 * chan));
+
+ return insn->n;
+}
+
+static int c6xdigio_pwm_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ val = (s->state >> (16 * chan)) & 0xffff;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = val;
+
+ return insn->n;
+}
+
+static int c6xdigio_encoder_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = c6xdigio_encoder_read(dev, chan);
+
+ /* munge two's complement value to offset binary */
+ data[i] = comedi_offset_munge(s, val);
+ }
+
+ return insn->n;
+}
+
+static void c6xdigio_init(struct comedi_device *dev)
+{
+ /* Initialize the PWM */
+ c6xdigio_write_data(dev, 0x70, 0x00);
+ c6xdigio_write_data(dev, 0x74, 0x80);
+ c6xdigio_write_data(dev, 0x70, 0x00);
+ c6xdigio_write_data(dev, 0x00, 0x80);
+
+ /* Reset the encoders */
+ c6xdigio_write_data(dev, 0x68, 0x00);
+ c6xdigio_write_data(dev, 0x6c, 0x80);
+ c6xdigio_write_data(dev, 0x68, 0x00);
+ c6xdigio_write_data(dev, 0x00, 0x80);
+}
+
+static const struct pnp_device_id c6xdigio_pnp_tbl[] = {
+ /* Standard LPT Printer Port */
+ {.id = "PNP0400", .driver_data = 0},
+ /* ECP Printer Port */
+ {.id = "PNP0401", .driver_data = 0},
+ {}
+};
+
+static struct pnp_driver c6xdigio_pnp_driver = {
+ .name = "c6xdigio",
+ .id_table = c6xdigio_pnp_tbl,
+};
+
+static int c6xdigio_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x03);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Make sure that PnP ports get activated */
+ pnp_register_driver(&c6xdigio_pnp_driver);
+
+ s = &dev->subdevices[0];
+ /* pwm output subdevice */
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 500;
+ s->range_table = &range_unknown;
+ s->insn_write = c6xdigio_pwm_insn_write;
+ s->insn_read = c6xdigio_pwm_insn_read;
+
+ s = &dev->subdevices[1];
+ /* encoder (counter) subdevice */
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = 2;
+ s->maxdata = 0xffffff;
+ s->range_table = &range_unknown;
+ s->insn_read = c6xdigio_encoder_insn_read;
+
+ /* I will call this init anyway but more than likely the DSP board */
+ /* will not be connected when device driver is loaded. */
+ c6xdigio_init(dev);
+
+ return 0;
+}
+
+static void c6xdigio_detach(struct comedi_device *dev)
+{
+ comedi_legacy_detach(dev);
+ pnp_unregister_driver(&c6xdigio_pnp_driver);
+}
+
+static struct comedi_driver c6xdigio_driver = {
+ .driver_name = "c6xdigio",
+ .module = THIS_MODULE,
+ .attach = c6xdigio_attach,
+ .detach = c6xdigio_detach,
+};
+module_comedi_driver(c6xdigio_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for the C6x_DIGIO DSP daughter card");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/cb_das16_cs.c b/drivers/staging/comedi/drivers/cb_das16_cs.c
new file mode 100644
index 000000000..ae84f2c0c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_das16_cs.c
@@ -0,0 +1,355 @@
+/*
+ comedi/drivers/das16cs.c
+ Driver for Computer Boards PC-CARD DAS16/16.
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000, 2001, 2002 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ PCMCIA support code for this driver is adapted from the dummy_cs.c
+ driver of the Linux PCMCIA Card Services package.
+
+ The initial developer of the original code is David A. Hinds
+ <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
+ are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
+
+*/
+/*
+Driver: cb_das16_cs
+Description: Computer Boards PC-CARD DAS16/16
+Devices: [ComputerBoards] PC-CARD DAS16/16 (cb_das16_cs), PC-CARD DAS16/16-AO
+Author: ds
+Updated: Mon, 04 Nov 2002 20:04:21 -0800
+Status: experimental
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include "../comedi_pcmcia.h"
+
+#include "comedi_8254.h"
+
+#define DAS16CS_ADC_DATA 0
+#define DAS16CS_DIO_MUX 2
+#define DAS16CS_MISC1 4
+#define DAS16CS_MISC2 6
+#define DAS16CS_TIMER_BASE 8
+#define DAS16CS_DIO 16
+
+struct das16cs_board {
+ const char *name;
+ int device_id;
+ int n_ao_chans;
+};
+
+static const struct das16cs_board das16cs_boards[] = {
+ {
+ .name = "PC-CARD DAS16/16-AO",
+ .device_id = 0x0039,
+ .n_ao_chans = 2,
+ }, {
+ .name = "PCM-DAS16s/16",
+ .device_id = 0x4009,
+ .n_ao_chans = 0,
+ }, {
+ .name = "PC-CARD DAS16/16",
+ .device_id = 0x0000, /* unknown */
+ .n_ao_chans = 0,
+ },
+};
+
+struct das16cs_private {
+ unsigned short status1;
+ unsigned short status2;
+};
+
+static const struct comedi_lrange das16cs_ai_range = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ }
+};
+
+static int das16cs_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + DAS16CS_MISC1);
+ if (status & 0x0080)
+ return 0;
+ return -EBUSY;
+}
+
+static int das16cs_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct das16cs_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ int range = CR_RANGE(insn->chanspec);
+ int aref = CR_AREF(insn->chanspec);
+ int ret;
+ int i;
+
+ outw(chan, dev->iobase + DAS16CS_DIO_MUX);
+
+ devpriv->status1 &= ~0xf320;
+ devpriv->status1 |= (aref == AREF_DIFF) ? 0 : 0x0020;
+ outw(devpriv->status1, dev->iobase + DAS16CS_MISC1);
+
+ devpriv->status2 &= ~0xff00;
+ switch (range) {
+ case 0:
+ devpriv->status2 |= 0x800;
+ break;
+ case 1:
+ devpriv->status2 |= 0x000;
+ break;
+ case 2:
+ devpriv->status2 |= 0x100;
+ break;
+ case 3:
+ devpriv->status2 |= 0x200;
+ break;
+ }
+ outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
+
+ for (i = 0; i < insn->n; i++) {
+ outw(0, dev->iobase + DAS16CS_ADC_DATA);
+
+ ret = comedi_timeout(dev, s, insn, das16cs_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = inw(dev->iobase + DAS16CS_ADC_DATA);
+ }
+
+ return i;
+}
+
+static int das16cs_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das16cs_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ unsigned short status1;
+ int bit;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ outw(devpriv->status1, dev->iobase + DAS16CS_MISC1);
+ udelay(1);
+
+ status1 = devpriv->status1 & ~0xf;
+ if (chan)
+ status1 |= 0x0001;
+ else
+ status1 |= 0x0008;
+
+ outw(status1, dev->iobase + DAS16CS_MISC1);
+ udelay(1);
+
+ for (bit = 15; bit >= 0; bit--) {
+ int b = (val >> bit) & 0x1;
+
+ b <<= 1;
+ outw(status1 | b | 0x0000, dev->iobase + DAS16CS_MISC1);
+ udelay(1);
+ outw(status1 | b | 0x0004, dev->iobase + DAS16CS_MISC1);
+ udelay(1);
+ }
+ /*
+ * Make both DAC0CS and DAC1CS high to load
+ * the new data and update analog the output
+ */
+ outw(status1 | 0x9, dev->iobase + DAS16CS_MISC1);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int das16cs_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + DAS16CS_DIO);
+
+ data[1] = inw(dev->iobase + DAS16CS_DIO);
+
+ return insn->n;
+}
+
+static int das16cs_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das16cs_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ devpriv->status2 &= ~0x00c0;
+ devpriv->status2 |= (s->io_bits & 0xf0) ? 0x0080 : 0;
+ devpriv->status2 |= (s->io_bits & 0x0f) ? 0x0040 : 0;
+
+ outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
+
+ return insn->n;
+}
+
+static const void *das16cs_find_boardinfo(struct comedi_device *dev,
+ struct pcmcia_device *link)
+{
+ const struct das16cs_board *board;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(das16cs_boards); i++) {
+ board = &das16cs_boards[i];
+ if (board->device_id == link->card_id)
+ return board;
+ }
+
+ return NULL;
+}
+
+static int das16cs_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ const struct das16cs_board *board;
+ struct das16cs_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ board = das16cs_find_boardinfo(dev, link);
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ link->priv = dev;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->pacer = comedi_8254_init(dev->iobase + DAS16CS_TIMER_BASE,
+ I8254_OSC_BASE_10MHZ, I8254_IO16, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ;
+ s->n_chan = 16;
+ s->maxdata = 0xffff;
+ s->range_table = &das16cs_ai_range;
+ s->len_chanlist = 16;
+ s->insn_read = das16cs_ai_rinsn;
+
+ s = &dev->subdevices[1];
+ /* analog output subdevice */
+ if (board->n_ao_chans) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_ao_chans;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = &das16cs_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* digital i/o subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das16cs_dio_insn_bits;
+ s->insn_config = das16cs_dio_insn_config;
+
+ return 0;
+}
+
+static struct comedi_driver driver_das16cs = {
+ .driver_name = "cb_das16_cs",
+ .module = THIS_MODULE,
+ .auto_attach = das16cs_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int das16cs_pcmcia_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_das16cs);
+}
+
+static const struct pcmcia_device_id das16cs_id_table[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x0039),
+ PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4009),
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, das16cs_id_table);
+
+static struct pcmcia_driver das16cs_driver = {
+ .name = "cb_das16_cs",
+ .owner = THIS_MODULE,
+ .id_table = das16cs_id_table,
+ .probe = das16cs_pcmcia_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_das16cs, das16cs_driver);
+
+MODULE_AUTHOR("David A. Schleef <ds@schleef.org>");
+MODULE_DESCRIPTION("Comedi driver for Computer Boards PC-CARD DAS16/16");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/cb_pcidas.c b/drivers/staging/comedi/drivers/cb_pcidas.c
new file mode 100644
index 000000000..e3591a582
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcidas.c
@@ -0,0 +1,1582 @@
+/*
+ comedi/drivers/cb_pcidas.c
+
+ Developed by Ivan Martinez and Frank Mori Hess, with valuable help from
+ David Schleef and the rest of the Comedi developers comunity.
+
+ Copyright (C) 2001-2003 Ivan Martinez <imr@oersted.dtu.dk>
+ Copyright (C) 2001,2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: cb_pcidas
+Description: MeasurementComputing PCI-DAS series
+ with the AMCC S5933 PCI controller
+Author: Ivan Martinez <imr@oersted.dtu.dk>,
+ Frank Mori Hess <fmhess@users.sourceforge.net>
+Updated: 2003-3-11
+Devices: [Measurement Computing] PCI-DAS1602/16 (cb_pcidas),
+ PCI-DAS1602/16jr, PCI-DAS1602/12, PCI-DAS1200, PCI-DAS1200jr,
+ PCI-DAS1000, PCI-DAS1001, PCI_DAS1002
+
+Status:
+ There are many reports of the driver being used with most of the
+ supported cards. Despite no detailed log is maintained, it can
+ be said that the driver is quite tested and stable.
+
+ The boards may be autocalibrated using the comedi_calibrate
+ utility.
+
+Configuration options: not applicable, uses PCI auto config
+
+For commands, the scanned channels must be consecutive
+(i.e. 4-5-6-7, 2-3-4,...), and must all have the same
+range and aref.
+
+AI Triggering:
+ For start_src == TRIG_EXT, the A/D EXTERNAL TRIGGER IN (pin 45) is used.
+ For 1602 series, the start_arg is interpreted as follows:
+ start_arg == 0 => gated trigger (level high)
+ start_arg == CR_INVERT => gated trigger (level low)
+ start_arg == CR_EDGE => Rising edge
+ start_arg == CR_EDGE | CR_INVERT => Falling edge
+ For the other boards the trigger will be done on rising edge
+*/
+/*
+
+TODO:
+
+analog triggering on 1602 series
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "8255.h"
+#include "amcc_s5933.h"
+
+#define AI_BUFFER_SIZE 1024 /* max ai fifo size */
+#define AO_BUFFER_SIZE 1024 /* max ao fifo size */
+#define NUM_CHANNELS_8800 8
+#define NUM_CHANNELS_7376 1
+#define NUM_CHANNELS_8402 2
+#define NUM_CHANNELS_DAC08 1
+
+/* Control/Status registers */
+#define INT_ADCFIFO 0 /* INTERRUPT / ADC FIFO register */
+#define INT_EOS 0x1 /* int end of scan */
+#define INT_FHF 0x2 /* int fifo half full */
+#define INT_FNE 0x3 /* int fifo not empty */
+#define INT_MASK 0x3 /* mask of int select bits */
+#define INTE 0x4 /* int enable */
+#define DAHFIE 0x8 /* dac half full int enable */
+#define EOAIE 0x10 /* end of acq. int enable */
+#define DAHFI 0x20 /* dac half full status / clear */
+#define EOAI 0x40 /* end of acq. int status / clear */
+#define INT 0x80 /* int status / clear */
+#define EOBI 0x200 /* end of burst int status */
+#define ADHFI 0x400 /* half-full int status */
+#define ADNEI 0x800 /* fifo not empty int status (latch) */
+#define ADNE 0x1000 /* fifo not empty status (realtime) */
+#define DAEMIE 0x1000 /* dac empty int enable */
+#define LADFUL 0x2000 /* fifo overflow / clear */
+#define DAEMI 0x4000 /* dac fifo empty int status / clear */
+
+#define ADCMUX_CONT 2 /* ADC CHANNEL MUX AND CONTROL reg */
+#define BEGIN_SCAN(x) ((x) & 0xf)
+#define END_SCAN(x) (((x) & 0xf) << 4)
+#define GAIN_BITS(x) (((x) & 0x3) << 8)
+#define UNIP 0x800 /* Analog front-end unipolar mode */
+#define SE 0x400 /* Inputs in single-ended mode */
+#define PACER_MASK 0x3000 /* pacer source bits */
+#define PACER_INT 0x1000 /* int. pacer */
+#define PACER_EXT_FALL 0x2000 /* ext. falling edge */
+#define PACER_EXT_RISE 0x3000 /* ext. rising edge */
+#define EOC 0x4000 /* adc not busy */
+
+#define TRIG_CONTSTAT 4 /* TRIGGER CONTROL/STATUS register */
+#define SW_TRIGGER 0x1 /* software start trigger */
+#define EXT_TRIGGER 0x2 /* ext. start trigger */
+#define ANALOG_TRIGGER 0x3 /* ext. analog trigger */
+#define TRIGGER_MASK 0x3 /* start trigger mask */
+#define TGPOL 0x04 /* invert trigger (1602 only) */
+#define TGSEL 0x08 /* edge/level trigerred (1602 only) */
+#define TGEN 0x10 /* enable external start trigger */
+#define BURSTE 0x20 /* burst mode enable */
+#define XTRCL 0x80 /* clear external trigger */
+
+#define CALIBRATION_REG 6 /* CALIBRATION register */
+#define SELECT_8800_BIT 0x100 /* select 8800 caldac */
+#define SELECT_TRIMPOT_BIT 0x200 /* select ad7376 trim pot */
+#define SELECT_DAC08_BIT 0x400 /* select dac08 caldac */
+#define CAL_SRC_BITS(x) (((x) & 0x7) << 11)
+#define CAL_EN_BIT 0x4000 /* calibration source enable */
+#define SERIAL_DATA_IN_BIT 0x8000 /* serial data bit going to caldac */
+
+#define DAC_CSR 0x8 /* dac control and status register */
+#define DACEN 0x02 /* dac enable */
+#define DAC_MODE_UPDATE_BOTH 0x80 /* update both dacs */
+
+static inline unsigned int DAC_RANGE(unsigned int channel, unsigned int range)
+{
+ return (range & 0x3) << (8 + 2 * (channel & 0x1));
+}
+
+static inline unsigned int DAC_RANGE_MASK(unsigned int channel)
+{
+ return 0x3 << (8 + 2 * (channel & 0x1));
+};
+
+/* bits for 1602 series only */
+#define DAC_EMPTY 0x1 /* fifo empty, read, write clear */
+#define DAC_START 0x4 /* start/arm fifo operations */
+#define DAC_PACER_MASK 0x18 /* bits that set pacer source */
+#define DAC_PACER_INT 0x8 /* int. pacing */
+#define DAC_PACER_EXT_FALL 0x10 /* ext. pacing, falling edge */
+#define DAC_PACER_EXT_RISE 0x18 /* ext. pacing, rising edge */
+
+static inline unsigned int DAC_CHAN_EN(unsigned int channel)
+{
+ return 1 << (5 + (channel & 0x1)); /* enable channel 0 or 1 */
+};
+
+/* analog input fifo */
+#define ADCDATA 0 /* ADC DATA register */
+#define ADCFIFOCLR 2 /* ADC FIFO CLEAR */
+
+/* pacer, counter, dio registers */
+#define ADC8254 0
+#define DIO_8255 4
+#define DAC8254 8
+
+/* analog output registers for 100x, 1200 series */
+static inline unsigned int DAC_DATA_REG(unsigned int channel)
+{
+ return 2 * (channel & 0x1);
+}
+
+/* analog output registers for 1602 series*/
+#define DACDATA 0 /* DAC DATA register */
+#define DACFIFOCLR 2 /* DAC FIFO CLEAR */
+
+#define IS_UNIPOLAR 0x4 /* unipolar range mask */
+
+/* analog input ranges for most boards */
+static const struct comedi_lrange cb_pcidas_ranges = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+/* pci-das1001 input ranges */
+static const struct comedi_lrange cb_pcidas_alt_ranges = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.01),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01)
+ }
+};
+
+/* analog output ranges */
+static const struct comedi_lrange cb_pcidas_ao_ranges = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+enum trimpot_model {
+ AD7376,
+ AD8402,
+};
+
+enum cb_pcidas_boardid {
+ BOARD_PCIDAS1602_16,
+ BOARD_PCIDAS1200,
+ BOARD_PCIDAS1602_12,
+ BOARD_PCIDAS1200_JR,
+ BOARD_PCIDAS1602_16_JR,
+ BOARD_PCIDAS1000,
+ BOARD_PCIDAS1001,
+ BOARD_PCIDAS1002,
+};
+
+struct cb_pcidas_board {
+ const char *name;
+ int ai_nchan; /* Inputs in single-ended mode */
+ int ai_bits; /* analog input resolution */
+ int ai_speed; /* fastest conversion period in ns */
+ int ao_nchan; /* number of analog out channels */
+ int has_ao_fifo; /* analog output has fifo */
+ int ao_scan_speed; /* analog output scan speed for 1602 series */
+ int fifo_size; /* number of samples fifo can hold */
+ const struct comedi_lrange *ranges;
+ enum trimpot_model trimpot;
+ unsigned has_dac08:1;
+ unsigned is_1602:1;
+};
+
+static const struct cb_pcidas_board cb_pcidas_boards[] = {
+ [BOARD_PCIDAS1602_16] = {
+ .name = "pci-das1602/16",
+ .ai_nchan = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 2,
+ .has_ao_fifo = 1,
+ .ao_scan_speed = 10000,
+ .fifo_size = 512,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD8402,
+ .has_dac08 = 1,
+ .is_1602 = 1,
+ },
+ [BOARD_PCIDAS1200] = {
+ .name = "pci-das1200",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 3200,
+ .ao_nchan = 2,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1602_12] = {
+ .name = "pci-das1602/12",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 3200,
+ .ao_nchan = 2,
+ .has_ao_fifo = 1,
+ .ao_scan_speed = 4000,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ .is_1602 = 1,
+ },
+ [BOARD_PCIDAS1200_JR] = {
+ .name = "pci-das1200/jr",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 3200,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1602_16_JR] = {
+ .name = "pci-das1602/16/jr",
+ .ai_nchan = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .fifo_size = 512,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD8402,
+ .has_dac08 = 1,
+ .is_1602 = 1,
+ },
+ [BOARD_PCIDAS1000] = {
+ .name = "pci-das1000",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 4000,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1001] = {
+ .name = "pci-das1001",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 6800,
+ .ao_nchan = 2,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_alt_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1002] = {
+ .name = "pci-das1002",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 6800,
+ .ao_nchan = 2,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+};
+
+struct cb_pcidas_private {
+ struct comedi_8254 *ao_pacer;
+ /* base addresses */
+ unsigned long s5933_config;
+ unsigned long control_status;
+ unsigned long adc_fifo;
+ unsigned long ao_registers;
+ /* bits to write to registers */
+ unsigned int adc_fifo_bits;
+ unsigned int s5933_intcsr_bits;
+ unsigned int ao_control_bits;
+ /* fifo buffers */
+ unsigned short ai_buffer[AI_BUFFER_SIZE];
+ unsigned short ao_buffer[AO_BUFFER_SIZE];
+ unsigned int calibration_source;
+};
+
+static inline unsigned int cal_enable_bits(struct comedi_device *dev)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ return CAL_EN_BIT | CAL_SRC_BITS(devpriv->calibration_source);
+}
+
+static int cb_pcidas_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = inw(devpriv->control_status + ADCMUX_CONT);
+ if (status & EOC)
+ return 0;
+ return -EBUSY;
+}
+
+static int cb_pcidas_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned int bits;
+ int ret;
+ int n;
+
+ /* enable calibration input if appropriate */
+ if (insn->chanspec & CR_ALT_SOURCE) {
+ outw(cal_enable_bits(dev),
+ devpriv->control_status + CALIBRATION_REG);
+ chan = 0;
+ } else {
+ outw(0, devpriv->control_status + CALIBRATION_REG);
+ }
+
+ /* set mux limits and gain */
+ bits = BEGIN_SCAN(chan) | END_SCAN(chan) | GAIN_BITS(range);
+ /* set unipolar/bipolar */
+ if (range & IS_UNIPOLAR)
+ bits |= UNIP;
+ /* set single-ended/differential */
+ if (aref != AREF_DIFF)
+ bits |= SE;
+ outw(bits, devpriv->control_status + ADCMUX_CONT);
+
+ /* clear fifo */
+ outw(0, devpriv->adc_fifo + ADCFIFOCLR);
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion */
+ outw(0, devpriv->adc_fifo + ADCDATA);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, cb_pcidas_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ data[n] = inw(devpriv->adc_fifo + ADCDATA);
+ }
+
+ /* return the number of samples read/written */
+ return n;
+}
+
+static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ int id = data[0];
+ unsigned int source = data[1];
+
+ switch (id) {
+ case INSN_CONFIG_ALT_SOURCE:
+ if (source >= 8) {
+ dev_err(dev->class_dev,
+ "invalid calibration source: %i\n",
+ source);
+ return -EINVAL;
+ }
+ devpriv->calibration_source = source;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return insn->n;
+}
+
+/* analog output insn for pcidas-1000 and 1200 series */
+static int cb_pcidas_ao_nofifo_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned long flags;
+
+ /* set channel and range */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->ao_control_bits &= (~DAC_MODE_UPDATE_BOTH &
+ ~DAC_RANGE_MASK(chan));
+ devpriv->ao_control_bits |= (DACEN | DAC_RANGE(chan, range));
+ outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* remember value for readback */
+ s->readback[chan] = data[0];
+
+ /* send data */
+ outw(data[0], devpriv->ao_registers + DAC_DATA_REG(chan));
+
+ return insn->n;
+}
+
+/* analog output insn for pcidas-1602 series */
+static int cb_pcidas_ao_fifo_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned long flags;
+
+ /* clear dac fifo */
+ outw(0, devpriv->ao_registers + DACFIFOCLR);
+
+ /* set channel and range */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->ao_control_bits &= (~DAC_CHAN_EN(0) & ~DAC_CHAN_EN(1) &
+ ~DAC_RANGE_MASK(chan) & ~DAC_PACER_MASK);
+ devpriv->ao_control_bits |= (DACEN | DAC_RANGE(chan, range) |
+ DAC_CHAN_EN(chan) | DAC_START);
+ outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* remember value for readback */
+ s->readback[chan] = data[0];
+
+ /* send data */
+ outw(data[0], devpriv->ao_registers + DACDATA);
+
+ return insn->n;
+}
+
+static int wait_for_nvram_ready(unsigned long s5933_base_addr)
+{
+ static const int timeout = 1000;
+ unsigned int i;
+
+ for (i = 0; i < timeout; i++) {
+ if ((inb(s5933_base_addr +
+ AMCC_OP_REG_MCSR_NVCMD) & MCSR_NV_BUSY)
+ == 0)
+ return 0;
+ udelay(1);
+ }
+ return -1;
+}
+
+static int nvram_read(struct comedi_device *dev, unsigned int address,
+ uint8_t *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned long iobase = devpriv->s5933_config;
+
+ if (wait_for_nvram_ready(iobase) < 0)
+ return -ETIMEDOUT;
+
+ outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_LOW_ADDR,
+ iobase + AMCC_OP_REG_MCSR_NVCMD);
+ outb(address & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
+ outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_HIGH_ADDR,
+ iobase + AMCC_OP_REG_MCSR_NVCMD);
+ outb((address >> 8) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
+ outb(MCSR_NV_ENABLE | MCSR_NV_READ, iobase + AMCC_OP_REG_MCSR_NVCMD);
+
+ if (wait_for_nvram_ready(iobase) < 0)
+ return -ETIMEDOUT;
+
+ *data = inb(iobase + AMCC_OP_REG_MCSR_NVDATA);
+
+ return 0;
+}
+
+static int eeprom_read_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ uint8_t nvram_data;
+ int retval;
+
+ retval = nvram_read(dev, CR_CHAN(insn->chanspec), &nvram_data);
+ if (retval < 0)
+ return retval;
+
+ data[0] = nvram_data;
+
+ return 1;
+}
+
+static void write_calibration_bitstream(struct comedi_device *dev,
+ unsigned int register_bits,
+ unsigned int bitstream,
+ unsigned int bitstream_length)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ static const int write_delay = 1;
+ unsigned int bit;
+
+ for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
+ if (bitstream & bit)
+ register_bits |= SERIAL_DATA_IN_BIT;
+ else
+ register_bits &= ~SERIAL_DATA_IN_BIT;
+ udelay(write_delay);
+ outw(register_bits, devpriv->control_status + CALIBRATION_REG);
+ }
+}
+
+static void caldac_8800_write(struct comedi_device *dev,
+ unsigned int chan, uint8_t val)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ static const int bitstream_length = 11;
+ unsigned int bitstream = ((chan & 0x7) << 8) | val;
+ static const int caldac_8800_udelay = 1;
+
+ write_calibration_bitstream(dev, cal_enable_bits(dev), bitstream,
+ bitstream_length);
+
+ udelay(caldac_8800_udelay);
+ outw(cal_enable_bits(dev) | SELECT_8800_BIT,
+ devpriv->control_status + CALIBRATION_REG);
+ udelay(caldac_8800_udelay);
+ outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG);
+}
+
+static int cb_pcidas_caldac_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ if (insn->n) {
+ unsigned int val = data[insn->n - 1];
+
+ if (s->readback[chan] != val) {
+ caldac_8800_write(dev, chan, val);
+ s->readback[chan] = val;
+ }
+ }
+
+ return insn->n;
+}
+
+/* 1602/16 pregain offset */
+static void dac08_write(struct comedi_device *dev, unsigned int value)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ value &= 0xff;
+ value |= cal_enable_bits(dev);
+
+ /* latch the new value into the caldac */
+ outw(value, devpriv->control_status + CALIBRATION_REG);
+ udelay(1);
+ outw(value | SELECT_DAC08_BIT,
+ devpriv->control_status + CALIBRATION_REG);
+ udelay(1);
+ outw(value, devpriv->control_status + CALIBRATION_REG);
+ udelay(1);
+}
+
+static int cb_pcidas_dac08_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ if (insn->n) {
+ unsigned int val = data[insn->n - 1];
+
+ if (s->readback[chan] != val) {
+ dac08_write(dev, val);
+ s->readback[chan] = val;
+ }
+ }
+
+ return insn->n;
+}
+
+static int trimpot_7376_write(struct comedi_device *dev, uint8_t value)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ static const int bitstream_length = 7;
+ unsigned int bitstream = value & 0x7f;
+ unsigned int register_bits;
+ static const int ad7376_udelay = 1;
+
+ register_bits = cal_enable_bits(dev) | SELECT_TRIMPOT_BIT;
+ udelay(ad7376_udelay);
+ outw(register_bits, devpriv->control_status + CALIBRATION_REG);
+
+ write_calibration_bitstream(dev, register_bits, bitstream,
+ bitstream_length);
+
+ udelay(ad7376_udelay);
+ outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG);
+
+ return 0;
+}
+
+/* For 1602/16 only
+ * ch 0 : adc gain
+ * ch 1 : adc postgain offset */
+static int trimpot_8402_write(struct comedi_device *dev, unsigned int channel,
+ uint8_t value)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ static const int bitstream_length = 10;
+ unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff);
+ unsigned int register_bits;
+ static const int ad8402_udelay = 1;
+
+ register_bits = cal_enable_bits(dev) | SELECT_TRIMPOT_BIT;
+ udelay(ad8402_udelay);
+ outw(register_bits, devpriv->control_status + CALIBRATION_REG);
+
+ write_calibration_bitstream(dev, register_bits, bitstream,
+ bitstream_length);
+
+ udelay(ad8402_udelay);
+ outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG);
+
+ return 0;
+}
+
+static void cb_pcidas_trimpot_write(struct comedi_device *dev,
+ unsigned int chan, unsigned int val)
+{
+ const struct cb_pcidas_board *thisboard = dev->board_ptr;
+
+ switch (thisboard->trimpot) {
+ case AD7376:
+ trimpot_7376_write(dev, val);
+ break;
+ case AD8402:
+ trimpot_8402_write(dev, chan, val);
+ break;
+ default:
+ dev_err(dev->class_dev, "driver bug?\n");
+ break;
+ }
+}
+
+static int cb_pcidas_trimpot_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ if (insn->n) {
+ unsigned int val = data[insn->n - 1];
+
+ if (s->readback[chan] != val) {
+ cb_pcidas_trimpot_write(dev, chan, val);
+ s->readback[chan] = val;
+ }
+ }
+
+ return insn->n;
+}
+
+static int cb_pcidas_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i) % s->n_chan) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int cb_pcidas_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct cb_pcidas_board *thisboard = dev->board_ptr;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW)
+ err |= -EINVAL;
+ if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW)
+ err |= -EINVAL;
+ if (cmd->start_src == TRIG_EXT &&
+ (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT))
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /* External trigger, only CR_EDGE and CR_INVERT flags allowed */
+ if ((cmd->start_arg
+ & (CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT))) != 0) {
+ cmd->start_arg &= ~(CR_FLAGS_MASK &
+ ~(CR_EDGE | CR_INVERT));
+ err |= -EINVAL;
+ }
+ if (!thisboard->is_1602 && (cmd->start_arg & CR_INVERT)) {
+ cmd->start_arg &= (CR_FLAGS_MASK & ~CR_INVERT);
+ err |= -EINVAL;
+ }
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ thisboard->ai_speed *
+ cmd->chanlist_len);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int cb_pcidas_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct cb_pcidas_board *thisboard = dev->board_ptr;
+ struct cb_pcidas_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int bits;
+ unsigned long flags;
+
+ /* make sure CAL_EN_BIT is disabled */
+ outw(0, devpriv->control_status + CALIBRATION_REG);
+ /* initialize before settings pacer source and count values */
+ outw(0, devpriv->control_status + TRIG_CONTSTAT);
+ /* clear fifo */
+ outw(0, devpriv->adc_fifo + ADCFIFOCLR);
+
+ /* set mux limits, gain and pacer source */
+ bits = BEGIN_SCAN(CR_CHAN(cmd->chanlist[0])) |
+ END_SCAN(CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])) |
+ GAIN_BITS(CR_RANGE(cmd->chanlist[0]));
+ /* set unipolar/bipolar */
+ if (CR_RANGE(cmd->chanlist[0]) & IS_UNIPOLAR)
+ bits |= UNIP;
+ /* set singleended/differential */
+ if (CR_AREF(cmd->chanlist[0]) != AREF_DIFF)
+ bits |= SE;
+ /* set pacer source */
+ if (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT)
+ bits |= PACER_EXT_RISE;
+ else
+ bits |= PACER_INT;
+ outw(bits, devpriv->control_status + ADCMUX_CONT);
+
+ /* load counters */
+ if (cmd->scan_begin_src == TRIG_TIMER ||
+ cmd->convert_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ }
+
+ /* enable interrupts */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->adc_fifo_bits |= INTE;
+ devpriv->adc_fifo_bits &= ~INT_MASK;
+ if (cmd->flags & CMDF_WAKE_EOS) {
+ if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1) {
+ /* interrupt end of burst */
+ devpriv->adc_fifo_bits |= INT_EOS;
+ } else {
+ /* interrupt fifo not empty */
+ devpriv->adc_fifo_bits |= INT_FNE;
+ }
+ } else {
+ /* interrupt fifo half full */
+ devpriv->adc_fifo_bits |= INT_FHF;
+ }
+
+ /* enable (and clear) interrupts */
+ outw(devpriv->adc_fifo_bits | EOAI | INT | LADFUL,
+ devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* set start trigger and burst mode */
+ bits = 0;
+ if (cmd->start_src == TRIG_NOW) {
+ bits |= SW_TRIGGER;
+ } else { /* TRIG_EXT */
+ bits |= EXT_TRIGGER | TGEN | XTRCL;
+ if (thisboard->is_1602) {
+ if (cmd->start_arg & CR_INVERT)
+ bits |= TGPOL;
+ if (cmd->start_arg & CR_EDGE)
+ bits |= TGSEL;
+ }
+ }
+ if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1)
+ bits |= BURSTE;
+ outw(bits, devpriv->control_status + TRIG_CONTSTAT);
+
+ return 0;
+}
+
+static int cb_pcidas_ao_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+
+ if (cmd->chanlist_len > 1) {
+ unsigned int chan1 = CR_CHAN(cmd->chanlist[1]);
+
+ if (chan0 != 0 || chan1 != 1) {
+ dev_dbg(dev->class_dev,
+ "channels must be ordered channel 0, channel 1 in chanlist\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int cb_pcidas_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct cb_pcidas_board *thisboard = dev->board_ptr;
+ struct cb_pcidas_private *devpriv = dev->private;
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ thisboard->ao_scan_speed);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ unsigned int arg = cmd->scan_begin_arg;
+
+ comedi_8254_cascade_ns_to_timer(devpriv->ao_pacer,
+ &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+/* cancel analog input command */
+static int cb_pcidas_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ /* disable interrupts */
+ devpriv->adc_fifo_bits &= ~INTE & ~EOAIE;
+ outw(devpriv->adc_fifo_bits, devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* disable start trigger source and burst mode */
+ outw(0, devpriv->control_status + TRIG_CONTSTAT);
+ /* software pacer source */
+ outw(0, devpriv->control_status + ADCMUX_CONT);
+
+ return 0;
+}
+
+static void cb_pcidas_ao_load_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int nsamples)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int nbytes;
+
+ nsamples = comedi_nsamples_left(s, nsamples);
+ nbytes = comedi_buf_read_samples(s, devpriv->ao_buffer, nsamples);
+
+ nsamples = comedi_bytes_to_samples(s, nbytes);
+ outsw(devpriv->ao_registers + DACDATA, devpriv->ao_buffer, nsamples);
+}
+
+static int cb_pcidas_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ const struct cb_pcidas_board *thisboard = dev->board_ptr;
+ struct cb_pcidas_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long flags;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ cb_pcidas_ao_load_fifo(dev, s, thisboard->fifo_size);
+
+ /* enable dac half-full and empty interrupts */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->adc_fifo_bits |= DAEMIE | DAHFIE;
+
+ /* enable and clear interrupts */
+ outw(devpriv->adc_fifo_bits | DAEMI | DAHFI,
+ devpriv->control_status + INT_ADCFIFO);
+
+ /* start dac */
+ devpriv->ao_control_bits |= DAC_START | DACEN | DAC_EMPTY;
+ outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ async->inttrig = NULL;
+
+ return 0;
+}
+
+static int cb_pcidas_ao_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int i;
+ unsigned long flags;
+
+ /* set channel limits, gain */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ /* enable channel */
+ devpriv->ao_control_bits |=
+ DAC_CHAN_EN(CR_CHAN(cmd->chanlist[i]));
+ /* set range */
+ devpriv->ao_control_bits |= DAC_RANGE(CR_CHAN(cmd->chanlist[i]),
+ CR_RANGE(cmd->
+ chanlist[i]));
+ }
+
+ /* disable analog out before settings pacer source and count values */
+ outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* clear fifo */
+ outw(0, devpriv->ao_registers + DACFIFOCLR);
+
+ /* load counters */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(devpriv->ao_pacer);
+ comedi_8254_pacer_enable(devpriv->ao_pacer, 1, 2, true);
+ }
+
+ /* set pacer source */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ devpriv->ao_control_bits |= DAC_PACER_INT;
+ break;
+ case TRIG_EXT:
+ devpriv->ao_control_bits |= DAC_PACER_EXT_RISE;
+ break;
+ default:
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ dev_err(dev->class_dev, "error setting dac pacer source\n");
+ return -1;
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ async->inttrig = cb_pcidas_ao_inttrig;
+
+ return 0;
+}
+
+/* cancel analog output command */
+static int cb_pcidas_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ /* disable interrupts */
+ devpriv->adc_fifo_bits &= ~DAHFIE & ~DAEMIE;
+ outw(devpriv->adc_fifo_bits, devpriv->control_status + INT_ADCFIFO);
+
+ /* disable output */
+ devpriv->ao_control_bits &= ~DACEN & ~DAC_PACER_MASK;
+ outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return 0;
+}
+
+static void handle_ao_interrupt(struct comedi_device *dev, unsigned int status)
+{
+ const struct cb_pcidas_board *thisboard = dev->board_ptr;
+ struct cb_pcidas_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long flags;
+
+ if (status & DAEMI) {
+ /* clear dac empty interrupt latch */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outw(devpriv->adc_fifo_bits | DAEMI,
+ devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ if (inw(devpriv->ao_registers + DAC_CSR) & DAC_EMPTY) {
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ } else {
+ dev_err(dev->class_dev, "dac fifo underflow\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+ }
+ } else if (status & DAHFI) {
+ cb_pcidas_ao_load_fifo(dev, s, thisboard->fifo_size / 2);
+
+ /* clear half-full interrupt latch */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outw(devpriv->adc_fifo_bits | DAHFI,
+ devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ }
+
+ comedi_handle_events(dev, s);
+}
+
+static irqreturn_t cb_pcidas_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = (struct comedi_device *)d;
+ const struct cb_pcidas_board *thisboard = dev->board_ptr;
+ struct cb_pcidas_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ int status, s5933_status;
+ int half_fifo = thisboard->fifo_size / 2;
+ unsigned int num_samples, i;
+ static const int timeout = 10000;
+ unsigned long flags;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ async = s->async;
+ cmd = &async->cmd;
+
+ s5933_status = inl(devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+ if ((INTCSR_INTR_ASSERTED & s5933_status) == 0)
+ return IRQ_NONE;
+
+ /* make sure mailbox 4 is empty */
+ inl_p(devpriv->s5933_config + AMCC_OP_REG_IMB4);
+ /* clear interrupt on amcc s5933 */
+ outl(devpriv->s5933_intcsr_bits | INTCSR_INBOX_INTR_STATUS,
+ devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+ status = inw(devpriv->control_status + INT_ADCFIFO);
+
+ /* check for analog output interrupt */
+ if (status & (DAHFI | DAEMI))
+ handle_ao_interrupt(dev, status);
+ /* check for analog input interrupts */
+ /* if fifo half-full */
+ if (status & ADHFI) {
+ /* read data */
+ num_samples = comedi_nsamples_left(s, half_fifo);
+ insw(devpriv->adc_fifo + ADCDATA, devpriv->ai_buffer,
+ num_samples);
+ comedi_buf_write_samples(s, devpriv->ai_buffer, num_samples);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+
+ /* clear half-full interrupt latch */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outw(devpriv->adc_fifo_bits | INT,
+ devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ /* else if fifo not empty */
+ } else if (status & (ADNEI | EOBI)) {
+ for (i = 0; i < timeout; i++) {
+ unsigned short val;
+
+ /* break if fifo is empty */
+ if ((ADNE & inw(devpriv->control_status +
+ INT_ADCFIFO)) == 0)
+ break;
+ val = inw(devpriv->adc_fifo);
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+ /* clear not-empty interrupt latch */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outw(devpriv->adc_fifo_bits | INT,
+ devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ } else if (status & EOAI) {
+ dev_err(dev->class_dev,
+ "bug! encountered end of acquisition interrupt?\n");
+ /* clear EOA interrupt latch */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outw(devpriv->adc_fifo_bits | EOAI,
+ devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ }
+ /* check for fifo overflow */
+ if (status & LADFUL) {
+ dev_err(dev->class_dev, "fifo overflow\n");
+ /* clear overflow interrupt latch */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ outw(devpriv->adc_fifo_bits | LADFUL,
+ devpriv->control_status + INT_ADCFIFO);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ async->events |= COMEDI_CB_ERROR;
+ }
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int cb_pcidas_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct cb_pcidas_board *thisboard = NULL;
+ struct cb_pcidas_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ int ret;
+
+ if (context < ARRAY_SIZE(cb_pcidas_boards))
+ thisboard = &cb_pcidas_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->s5933_config = pci_resource_start(pcidev, 0);
+ devpriv->control_status = pci_resource_start(pcidev, 1);
+ devpriv->adc_fifo = pci_resource_start(pcidev, 2);
+ dev->iobase = pci_resource_start(pcidev, 3);
+ if (thisboard->ao_nchan)
+ devpriv->ao_registers = pci_resource_start(pcidev, 4);
+
+ /* disable and clear interrupts on amcc s5933 */
+ outl(INTCSR_INBOX_INTR_STATUS,
+ devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+ ret = request_irq(pcidev->irq, cb_pcidas_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret) {
+ dev_dbg(dev->class_dev, "unable to allocate irq %d\n",
+ pcidev->irq);
+ return ret;
+ }
+ dev->irq = pcidev->irq;
+
+ dev->pacer = comedi_8254_init(dev->iobase + ADC8254,
+ I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ devpriv->ao_pacer = comedi_8254_init(dev->iobase + DAC8254,
+ I8254_OSC_BASE_10MHZ,
+ I8254_IO8, 0);
+ if (!devpriv->ao_pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 7);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ;
+ /* WARNING: Number of inputs in differential mode is ignored */
+ s->n_chan = thisboard->ai_nchan;
+ s->len_chanlist = thisboard->ai_nchan;
+ s->maxdata = (1 << thisboard->ai_bits) - 1;
+ s->range_table = thisboard->ranges;
+ s->insn_read = cb_pcidas_ai_rinsn;
+ s->insn_config = ai_config_insn;
+ s->do_cmd = cb_pcidas_ai_cmd;
+ s->do_cmdtest = cb_pcidas_ai_cmdtest;
+ s->cancel = cb_pcidas_cancel;
+
+ /* analog output subdevice */
+ s = &dev->subdevices[1];
+ if (thisboard->ao_nchan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = thisboard->ao_nchan;
+ /*
+ * analog out resolution is the same as
+ * analog input resolution, so use ai_bits
+ */
+ s->maxdata = (1 << thisboard->ai_bits) - 1;
+ s->range_table = &cb_pcidas_ao_ranges;
+ /* default to no fifo (*insn_write) */
+ s->insn_write = cb_pcidas_ao_nofifo_winsn;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ if (thisboard->has_ao_fifo) {
+ dev->write_subdev = s;
+ s->subdev_flags |= SDF_CMD_WRITE;
+ /* use fifo (*insn_write) instead */
+ s->insn_write = cb_pcidas_ao_fifo_winsn;
+ s->do_cmdtest = cb_pcidas_ao_cmdtest;
+ s->do_cmd = cb_pcidas_ao_cmd;
+ s->cancel = cb_pcidas_ao_cancel;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* 8255 */
+ s = &dev->subdevices[2];
+ ret = subdev_8255_init(dev, s, NULL, DIO_8255);
+ if (ret)
+ return ret;
+
+ /* serial EEPROM, */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_MEMORY;
+ s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
+ s->n_chan = 256;
+ s->maxdata = 0xff;
+ s->insn_read = eeprom_read_insn;
+
+ /* 8800 caldac */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = NUM_CHANNELS_8800;
+ s->maxdata = 0xff;
+ s->insn_write = cb_pcidas_caldac_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++) {
+ caldac_8800_write(dev, i, s->maxdata / 2);
+ s->readback[i] = s->maxdata / 2;
+ }
+
+ /* trim potentiometer */
+ s = &dev->subdevices[5];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ if (thisboard->trimpot == AD7376) {
+ s->n_chan = NUM_CHANNELS_7376;
+ s->maxdata = 0x7f;
+ } else {
+ s->n_chan = NUM_CHANNELS_8402;
+ s->maxdata = 0xff;
+ }
+ s->insn_write = cb_pcidas_trimpot_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++) {
+ cb_pcidas_trimpot_write(dev, i, s->maxdata / 2);
+ s->readback[i] = s->maxdata / 2;
+ }
+
+ /* dac08 caldac */
+ s = &dev->subdevices[6];
+ if (thisboard->has_dac08) {
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = NUM_CHANNELS_DAC08;
+ s->maxdata = 0xff;
+ s->insn_write = cb_pcidas_dac08_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++) {
+ dac08_write(dev, s->maxdata / 2);
+ s->readback[i] = s->maxdata / 2;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* make sure mailbox 4 is empty */
+ inl(devpriv->s5933_config + AMCC_OP_REG_IMB4);
+ /* Set bits to enable incoming mailbox interrupts on amcc s5933. */
+ devpriv->s5933_intcsr_bits =
+ INTCSR_INBOX_BYTE(3) | INTCSR_INBOX_SELECT(3) |
+ INTCSR_INBOX_FULL_INT;
+ /* clear and enable interrupt on amcc s5933 */
+ outl(devpriv->s5933_intcsr_bits | INTCSR_INBOX_INTR_STATUS,
+ devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+ return 0;
+}
+
+static void cb_pcidas_detach(struct comedi_device *dev)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->s5933_config)
+ outl(INTCSR_INBOX_INTR_STATUS,
+ devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+ kfree(devpriv->ao_pacer);
+ }
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver cb_pcidas_driver = {
+ .driver_name = "cb_pcidas",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcidas_auto_attach,
+ .detach = cb_pcidas_detach,
+};
+
+static int cb_pcidas_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcidas_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcidas_pci_table[] = {
+ { PCI_VDEVICE(CB, 0x0001), BOARD_PCIDAS1602_16 },
+ { PCI_VDEVICE(CB, 0x000f), BOARD_PCIDAS1200 },
+ { PCI_VDEVICE(CB, 0x0010), BOARD_PCIDAS1602_12 },
+ { PCI_VDEVICE(CB, 0x0019), BOARD_PCIDAS1200_JR },
+ { PCI_VDEVICE(CB, 0x001c), BOARD_PCIDAS1602_16_JR },
+ { PCI_VDEVICE(CB, 0x004c), BOARD_PCIDAS1000 },
+ { PCI_VDEVICE(CB, 0x001a), BOARD_PCIDAS1001 },
+ { PCI_VDEVICE(CB, 0x001b), BOARD_PCIDAS1002 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcidas_pci_table);
+
+static struct pci_driver cb_pcidas_pci_driver = {
+ .name = "cb_pcidas",
+ .id_table = cb_pcidas_pci_table,
+ .probe = cb_pcidas_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcidas_driver, cb_pcidas_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/cb_pcidas64.c b/drivers/staging/comedi/drivers/cb_pcidas64.c
new file mode 100644
index 000000000..a94c33c3d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcidas64.c
@@ -0,0 +1,4079 @@
+/*
+ comedi/drivers/cb_pcidas64.c
+ This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS
+ 64xx, 60xx, and 4020 cards.
+
+ Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ Copyright (C) 2001, 2002 Frank Mori Hess
+
+ Thanks also go to the following people:
+
+ Steve Rosenbluth, for providing the source code for
+ his pci-das6402 driver, and source code for working QNX pci-6402
+ drivers by Greg Laird and Mariusz Bogacz. None of the code was
+ used directly here, but it was useful as an additional source of
+ documentation on how to program the boards.
+
+ John Sims, for much testing and feedback on pcidas-4020 support.
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+ * Driver: cb_pcidas64
+ * Description: MeasurementComputing PCI-DAS64xx, 60XX, and 4020 series
+ * with the PLX 9080 PCI controller
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ * Updated: Fri, 02 Nov 2012 18:58:55 +0000
+ * Devices: [Measurement Computing] PCI-DAS6402/16 (cb_pcidas64),
+ * PCI-DAS6402/12, PCI-DAS64/M1/16, PCI-DAS64/M2/16,
+ * PCI-DAS64/M3/16, PCI-DAS6402/16/JR, PCI-DAS64/M1/16/JR,
+ * PCI-DAS64/M2/16/JR, PCI-DAS64/M3/16/JR, PCI-DAS64/M1/14,
+ * PCI-DAS64/M2/14, PCI-DAS64/M3/14, PCI-DAS6013, PCI-DAS6014,
+ * PCI-DAS6023, PCI-DAS6025, PCI-DAS6030,
+ * PCI-DAS6031, PCI-DAS6032, PCI-DAS6033, PCI-DAS6034,
+ * PCI-DAS6035, PCI-DAS6036, PCI-DAS6040, PCI-DAS6052,
+ * PCI-DAS6070, PCI-DAS6071, PCI-DAS4020/12
+ *
+ * Configuration options:
+ * None.
+ *
+ * Manual attachment of PCI cards with the comedi_config utility is not
+ * supported by this driver; they are attached automatically.
+ *
+ * These boards may be autocalibrated with the comedi_calibrate utility.
+ *
+ * To select the bnc trigger input on the 4020 (instead of the dio input),
+ * specify a nonzero channel in the chanspec. If you wish to use an external
+ * master clock on the 4020, you may do so by setting the scan_begin_src
+ * to TRIG_OTHER, and using an INSN_CONFIG_TIMER_1 configuration insn
+ * to configure the divisor to use for the external clock.
+ *
+ * Some devices are not identified because the PCI device IDs are not yet
+ * known. If you have such a board, please let the maintainers know.
+ */
+
+/*
+
+TODO:
+ make it return error if user attempts an ai command that uses the
+ external queue, and an ao command simultaneously user counter subdevice
+ there are a number of boards this driver will support when they are
+ fully released, but does not yet since the pci device id numbers
+ are not yet available.
+
+ support prescaled 100khz clock for slow pacing (not available on 6000
+ series?)
+
+ make ao fifo size adjustable like ai fifo
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "8255.h"
+#include "plx9080.h"
+
+#define TIMER_BASE 25 /* 40MHz master clock */
+/* 100kHz 'prescaled' clock for slow acquisition,
+ * maybe I'll support this someday */
+#define PRESCALED_TIMER_BASE 10000
+#define DMA_BUFFER_SIZE 0x1000
+
+/* maximum value that can be loaded into board's 24-bit counters*/
+static const int max_counter_value = 0xffffff;
+
+/* PCI-DAS64xxx base addresses */
+
+/* devpriv->main_iobase registers */
+enum write_only_registers {
+ INTR_ENABLE_REG = 0x0, /* interrupt enable register */
+ HW_CONFIG_REG = 0x2, /* hardware config register */
+ DAQ_SYNC_REG = 0xc,
+ DAQ_ATRIG_LOW_4020_REG = 0xc,
+ ADC_CONTROL0_REG = 0x10, /* adc control register 0 */
+ ADC_CONTROL1_REG = 0x12, /* adc control register 1 */
+ CALIBRATION_REG = 0x14,
+ /* lower 16 bits of adc sample interval counter */
+ ADC_SAMPLE_INTERVAL_LOWER_REG = 0x16,
+ /* upper 8 bits of adc sample interval counter */
+ ADC_SAMPLE_INTERVAL_UPPER_REG = 0x18,
+ /* lower 16 bits of delay interval counter */
+ ADC_DELAY_INTERVAL_LOWER_REG = 0x1a,
+ /* upper 8 bits of delay interval counter */
+ ADC_DELAY_INTERVAL_UPPER_REG = 0x1c,
+ /* lower 16 bits of hardware conversion/scan counter */
+ ADC_COUNT_LOWER_REG = 0x1e,
+ /* upper 8 bits of hardware conversion/scan counter */
+ ADC_COUNT_UPPER_REG = 0x20,
+ ADC_START_REG = 0x22, /* software trigger to start acquisition */
+ ADC_CONVERT_REG = 0x24, /* initiates single conversion */
+ ADC_QUEUE_CLEAR_REG = 0x26, /* clears adc queue */
+ ADC_QUEUE_LOAD_REG = 0x28, /* loads adc queue */
+ ADC_BUFFER_CLEAR_REG = 0x2a,
+ /* high channel for internal queue, use adc_chan_bits() inline above */
+ ADC_QUEUE_HIGH_REG = 0x2c,
+ DAC_CONTROL0_REG = 0x50, /* dac control register 0 */
+ DAC_CONTROL1_REG = 0x52, /* dac control register 0 */
+ /* lower 16 bits of dac sample interval counter */
+ DAC_SAMPLE_INTERVAL_LOWER_REG = 0x54,
+ /* upper 8 bits of dac sample interval counter */
+ DAC_SAMPLE_INTERVAL_UPPER_REG = 0x56,
+ DAC_SELECT_REG = 0x60,
+ DAC_START_REG = 0x64,
+ DAC_BUFFER_CLEAR_REG = 0x66, /* clear dac buffer */
+};
+
+static inline unsigned int dac_convert_reg(unsigned int channel)
+{
+ return 0x70 + (2 * (channel & 0x1));
+}
+
+static inline unsigned int dac_lsb_4020_reg(unsigned int channel)
+{
+ return 0x70 + (4 * (channel & 0x1));
+}
+
+static inline unsigned int dac_msb_4020_reg(unsigned int channel)
+{
+ return 0x72 + (4 * (channel & 0x1));
+}
+
+enum read_only_registers {
+ /* hardware status register,
+ * reading this apparently clears pending interrupts as well */
+ HW_STATUS_REG = 0x0,
+ PIPE1_READ_REG = 0x4,
+ ADC_READ_PNTR_REG = 0x8,
+ LOWER_XFER_REG = 0x10,
+ ADC_WRITE_PNTR_REG = 0xc,
+ PREPOST_REG = 0x14,
+};
+
+enum read_write_registers {
+ I8255_4020_REG = 0x48, /* 8255 offset, for 4020 only */
+ /* external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG */
+ ADC_QUEUE_FIFO_REG = 0x100,
+ ADC_FIFO_REG = 0x200, /* adc data fifo */
+ /* dac data fifo, has weird interactions with external channel queue */
+ DAC_FIFO_REG = 0x300,
+};
+
+/* dev->mmio registers */
+enum dio_counter_registers {
+ DIO_8255_OFFSET = 0x0,
+ DO_REG = 0x20,
+ DI_REG = 0x28,
+ DIO_DIRECTION_60XX_REG = 0x40,
+ DIO_DATA_60XX_REG = 0x48,
+};
+
+/* bit definitions for write-only registers */
+
+enum intr_enable_contents {
+ ADC_INTR_SRC_MASK = 0x3, /* adc interrupt source mask */
+ ADC_INTR_QFULL_BITS = 0x0, /* interrupt fifo quarter full */
+ ADC_INTR_EOC_BITS = 0x1, /* interrupt end of conversion */
+ ADC_INTR_EOSCAN_BITS = 0x2, /* interrupt end of scan */
+ ADC_INTR_EOSEQ_BITS = 0x3, /* interrupt end of sequence mask */
+ EN_ADC_INTR_SRC_BIT = 0x4, /* enable adc interrupt source */
+ EN_ADC_DONE_INTR_BIT = 0x8, /* enable adc acquisition done intr */
+ DAC_INTR_SRC_MASK = 0x30,
+ DAC_INTR_QEMPTY_BITS = 0x0,
+ DAC_INTR_HIGH_CHAN_BITS = 0x10,
+ EN_DAC_INTR_SRC_BIT = 0x40, /* enable dac interrupt source */
+ EN_DAC_DONE_INTR_BIT = 0x80,
+ EN_ADC_ACTIVE_INTR_BIT = 0x200, /* enable adc active interrupt */
+ EN_ADC_STOP_INTR_BIT = 0x400, /* enable adc stop trigger interrupt */
+ EN_DAC_ACTIVE_INTR_BIT = 0x800, /* enable dac active interrupt */
+ EN_DAC_UNDERRUN_BIT = 0x4000, /* enable dac underrun status bit */
+ EN_ADC_OVERRUN_BIT = 0x8000, /* enable adc overrun status bit */
+};
+
+enum hw_config_contents {
+ MASTER_CLOCK_4020_MASK = 0x3, /* master clock source mask for 4020 */
+ INTERNAL_CLOCK_4020_BITS = 0x1, /* use 40 MHz internal master clock */
+ BNC_CLOCK_4020_BITS = 0x2, /* use BNC input for master clock */
+ EXT_CLOCK_4020_BITS = 0x3, /* use dio input for master clock */
+ EXT_QUEUE_BIT = 0x200, /* use external channel/gain queue */
+ /* use 225 nanosec strobe when loading dac instead of 50 nanosec */
+ SLOW_DAC_BIT = 0x400,
+ /* bit with unknown function yet given as default value in pci-das64
+ * manual */
+ HW_CONFIG_DUMMY_BITS = 0x2000,
+ /* bit selects channels 1/0 for analog input/output, otherwise 0/1 */
+ DMA_CH_SELECT_BIT = 0x8000,
+ FIFO_SIZE_REG = 0x4, /* allows adjustment of fifo sizes */
+ DAC_FIFO_SIZE_MASK = 0xff00, /* bits that set dac fifo size */
+ DAC_FIFO_BITS = 0xf800, /* 8k sample ao fifo */
+};
+#define DAC_FIFO_SIZE 0x2000
+
+enum daq_atrig_low_4020_contents {
+ /* use trig/ext clk bnc input for analog gate signal */
+ EXT_AGATE_BNC_BIT = 0x8000,
+ /* use trig/ext clk bnc input for external stop trigger signal */
+ EXT_STOP_TRIG_BNC_BIT = 0x4000,
+ /* use trig/ext clk bnc input for external start trigger signal */
+ EXT_START_TRIG_BNC_BIT = 0x2000,
+};
+
+static inline uint16_t analog_trig_low_threshold_bits(uint16_t threshold)
+{
+ return threshold & 0xfff;
+}
+
+enum adc_control0_contents {
+ ADC_GATE_SRC_MASK = 0x3, /* bits that select gate */
+ ADC_SOFT_GATE_BITS = 0x1, /* software gate */
+ ADC_EXT_GATE_BITS = 0x2, /* external digital gate */
+ ADC_ANALOG_GATE_BITS = 0x3, /* analog level gate */
+ ADC_GATE_LEVEL_BIT = 0x4, /* level-sensitive gate (for digital) */
+ ADC_GATE_POLARITY_BIT = 0x8, /* gate active low */
+ ADC_START_TRIG_SOFT_BITS = 0x10,
+ ADC_START_TRIG_EXT_BITS = 0x20,
+ ADC_START_TRIG_ANALOG_BITS = 0x30,
+ ADC_START_TRIG_MASK = 0x30,
+ ADC_START_TRIG_FALLING_BIT = 0x40, /* trig 1 uses falling edge */
+ /* external pacing uses falling edge */
+ ADC_EXT_CONV_FALLING_BIT = 0x800,
+ /* enable hardware scan counter */
+ ADC_SAMPLE_COUNTER_EN_BIT = 0x1000,
+ ADC_DMA_DISABLE_BIT = 0x4000, /* disables dma */
+ ADC_ENABLE_BIT = 0x8000, /* master adc enable */
+};
+
+enum adc_control1_contents {
+ /* should be set for boards with > 16 channels */
+ ADC_QUEUE_CONFIG_BIT = 0x1,
+ CONVERT_POLARITY_BIT = 0x10,
+ EOC_POLARITY_BIT = 0x20,
+ ADC_SW_GATE_BIT = 0x40, /* software gate of adc */
+ ADC_DITHER_BIT = 0x200, /* turn on extra noise for dithering */
+ RETRIGGER_BIT = 0x800,
+ ADC_LO_CHANNEL_4020_MASK = 0x300,
+ ADC_HI_CHANNEL_4020_MASK = 0xc00,
+ TWO_CHANNEL_4020_BITS = 0x1000, /* two channel mode for 4020 */
+ FOUR_CHANNEL_4020_BITS = 0x2000, /* four channel mode for 4020 */
+ CHANNEL_MODE_4020_MASK = 0x3000,
+ ADC_MODE_MASK = 0xf000,
+};
+
+static inline uint16_t adc_lo_chan_4020_bits(unsigned int channel)
+{
+ return (channel & 0x3) << 8;
+};
+
+static inline uint16_t adc_hi_chan_4020_bits(unsigned int channel)
+{
+ return (channel & 0x3) << 10;
+};
+
+static inline uint16_t adc_mode_bits(unsigned int mode)
+{
+ return (mode & 0xf) << 12;
+};
+
+enum calibration_contents {
+ SELECT_8800_BIT = 0x1,
+ SELECT_8402_64XX_BIT = 0x2,
+ SELECT_1590_60XX_BIT = 0x2,
+ CAL_EN_64XX_BIT = 0x40, /* calibration enable for 64xx series */
+ SERIAL_DATA_IN_BIT = 0x80,
+ SERIAL_CLOCK_BIT = 0x100,
+ CAL_EN_60XX_BIT = 0x200, /* calibration enable for 60xx series */
+ CAL_GAIN_BIT = 0x800,
+};
+
+/* calibration sources for 6025 are:
+ * 0 : ground
+ * 1 : 10V
+ * 2 : 5V
+ * 3 : 0.5V
+ * 4 : 0.05V
+ * 5 : ground
+ * 6 : dac channel 0
+ * 7 : dac channel 1
+ */
+
+static inline uint16_t adc_src_bits(unsigned int source)
+{
+ return (source & 0xf) << 3;
+};
+
+static inline uint16_t adc_convert_chan_4020_bits(unsigned int channel)
+{
+ return (channel & 0x3) << 8;
+};
+
+enum adc_queue_load_contents {
+ UNIP_BIT = 0x800, /* unipolar/bipolar bit */
+ ADC_SE_DIFF_BIT = 0x1000, /* single-ended/ differential bit */
+ /* non-referenced single-ended (common-mode input) */
+ ADC_COMMON_BIT = 0x2000,
+ QUEUE_EOSEQ_BIT = 0x4000, /* queue end of sequence */
+ QUEUE_EOSCAN_BIT = 0x8000, /* queue end of scan */
+};
+
+static inline uint16_t adc_chan_bits(unsigned int channel)
+{
+ return channel & 0x3f;
+};
+
+enum dac_control0_contents {
+ DAC_ENABLE_BIT = 0x8000, /* dac controller enable bit */
+ DAC_CYCLIC_STOP_BIT = 0x4000,
+ DAC_WAVEFORM_MODE_BIT = 0x100,
+ DAC_EXT_UPDATE_FALLING_BIT = 0x80,
+ DAC_EXT_UPDATE_ENABLE_BIT = 0x40,
+ WAVEFORM_TRIG_MASK = 0x30,
+ WAVEFORM_TRIG_DISABLED_BITS = 0x0,
+ WAVEFORM_TRIG_SOFT_BITS = 0x10,
+ WAVEFORM_TRIG_EXT_BITS = 0x20,
+ WAVEFORM_TRIG_ADC1_BITS = 0x30,
+ WAVEFORM_TRIG_FALLING_BIT = 0x8,
+ WAVEFORM_GATE_LEVEL_BIT = 0x4,
+ WAVEFORM_GATE_ENABLE_BIT = 0x2,
+ WAVEFORM_GATE_SELECT_BIT = 0x1,
+};
+
+enum dac_control1_contents {
+ DAC_WRITE_POLARITY_BIT = 0x800, /* board-dependent setting */
+ DAC1_EXT_REF_BIT = 0x200,
+ DAC0_EXT_REF_BIT = 0x100,
+ DAC_OUTPUT_ENABLE_BIT = 0x80, /* dac output enable bit */
+ DAC_UPDATE_POLARITY_BIT = 0x40, /* board-dependent setting */
+ DAC_SW_GATE_BIT = 0x20,
+ DAC1_UNIPOLAR_BIT = 0x8,
+ DAC0_UNIPOLAR_BIT = 0x2,
+};
+
+/* bit definitions for read-only registers */
+enum hw_status_contents {
+ DAC_UNDERRUN_BIT = 0x1,
+ ADC_OVERRUN_BIT = 0x2,
+ DAC_ACTIVE_BIT = 0x4,
+ ADC_ACTIVE_BIT = 0x8,
+ DAC_INTR_PENDING_BIT = 0x10,
+ ADC_INTR_PENDING_BIT = 0x20,
+ DAC_DONE_BIT = 0x40,
+ ADC_DONE_BIT = 0x80,
+ EXT_INTR_PENDING_BIT = 0x100,
+ ADC_STOP_BIT = 0x200,
+};
+
+static inline uint16_t pipe_full_bits(uint16_t hw_status_bits)
+{
+ return (hw_status_bits >> 10) & 0x3;
+};
+
+static inline unsigned int dma_chain_flag_bits(uint16_t prepost_bits)
+{
+ return (prepost_bits >> 6) & 0x3;
+}
+
+static inline unsigned int adc_upper_read_ptr_code(uint16_t prepost_bits)
+{
+ return (prepost_bits >> 12) & 0x3;
+}
+
+static inline unsigned int adc_upper_write_ptr_code(uint16_t prepost_bits)
+{
+ return (prepost_bits >> 14) & 0x3;
+}
+
+/* I2C addresses for 4020 */
+enum i2c_addresses {
+ RANGE_CAL_I2C_ADDR = 0x20,
+ CALDAC0_I2C_ADDR = 0xc,
+ CALDAC1_I2C_ADDR = 0xd,
+};
+
+enum range_cal_i2c_contents {
+ /* bits that set what source the adc converter measures */
+ ADC_SRC_4020_MASK = 0x70,
+ /* make bnc trig/ext clock threshold 0V instead of 2.5V */
+ BNC_TRIG_THRESHOLD_0V_BIT = 0x80,
+};
+
+static inline uint8_t adc_src_4020_bits(unsigned int source)
+{
+ return (source << 4) & ADC_SRC_4020_MASK;
+};
+
+static inline uint8_t attenuate_bit(unsigned int channel)
+{
+ /* attenuate channel (+-5V input range) */
+ return 1 << (channel & 0x3);
+};
+
+/* analog input ranges for 64xx boards */
+static const struct comedi_lrange ai_ranges_64xx = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const uint8_t ai_range_code_64xx[8] = {
+ 0x0, 0x1, 0x2, 0x3, /* bipolar 10, 5, 2,5, 1.25 */
+ 0x8, 0x9, 0xa, 0xb /* unipolar 10, 5, 2.5, 1.25 */
+};
+
+/* analog input ranges for 64-Mx boards */
+static const struct comedi_lrange ai_ranges_64_mx = {
+ 7, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const uint8_t ai_range_code_64_mx[7] = {
+ 0x0, 0x1, 0x2, 0x3, /* bipolar 5, 2.5, 1.25, 0.625 */
+ 0x9, 0xa, 0xb /* unipolar 5, 2.5, 1.25 */
+};
+
+/* analog input ranges for 60xx boards */
+static const struct comedi_lrange ai_ranges_60xx = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05)
+ }
+};
+
+static const uint8_t ai_range_code_60xx[4] = {
+ 0x0, 0x1, 0x4, 0x7 /* bipolar 10, 5, 0.5, 0.05 */
+};
+
+/* analog input ranges for 6030, etc boards */
+static const struct comedi_lrange ai_ranges_6030 = {
+ 14, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.2),
+ BIP_RANGE(0.1),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.2),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const uint8_t ai_range_code_6030[14] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, /* bip 10, 5, 2, 1, 0.5, 0.2, 0.1 */
+ 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf /* uni 10, 5, 2, 1, 0.5, 0.2, 0.1 */
+};
+
+/* analog input ranges for 6052, etc boards */
+static const struct comedi_lrange ai_ranges_6052 = {
+ 15, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.25),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.2),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const uint8_t ai_range_code_6052[15] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, /* bipolar 10 ... 0.05 */
+ 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf /* unipolar 10 ... 0.1 */
+};
+
+/* analog input ranges for 4020 board */
+static const struct comedi_lrange ai_ranges_4020 = {
+ 2, {
+ BIP_RANGE(5),
+ BIP_RANGE(1)
+ }
+};
+
+/* analog output ranges */
+static const struct comedi_lrange ao_ranges_64xx = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+static const int ao_range_code_64xx[] = {
+ 0x0,
+ 0x1,
+ 0x2,
+ 0x3,
+};
+
+static const int ao_range_code_60xx[] = {
+ 0x0,
+};
+
+static const struct comedi_lrange ao_ranges_6030 = {
+ 2, {
+ BIP_RANGE(10),
+ UNI_RANGE(10)
+ }
+};
+
+static const int ao_range_code_6030[] = {
+ 0x0,
+ 0x2,
+};
+
+static const struct comedi_lrange ao_ranges_4020 = {
+ 2, {
+ BIP_RANGE(5),
+ BIP_RANGE(10)
+ }
+};
+
+static const int ao_range_code_4020[] = {
+ 0x1,
+ 0x0,
+};
+
+enum register_layout {
+ LAYOUT_60XX,
+ LAYOUT_64XX,
+ LAYOUT_4020,
+};
+
+struct hw_fifo_info {
+ unsigned int num_segments;
+ unsigned int max_segment_length;
+ unsigned int sample_packing_ratio;
+ uint16_t fifo_size_reg_mask;
+};
+
+enum pcidas64_boardid {
+ BOARD_PCIDAS6402_16,
+ BOARD_PCIDAS6402_12,
+ BOARD_PCIDAS64_M1_16,
+ BOARD_PCIDAS64_M2_16,
+ BOARD_PCIDAS64_M3_16,
+ BOARD_PCIDAS6013,
+ BOARD_PCIDAS6014,
+ BOARD_PCIDAS6023,
+ BOARD_PCIDAS6025,
+ BOARD_PCIDAS6030,
+ BOARD_PCIDAS6031,
+ BOARD_PCIDAS6032,
+ BOARD_PCIDAS6033,
+ BOARD_PCIDAS6034,
+ BOARD_PCIDAS6035,
+ BOARD_PCIDAS6036,
+ BOARD_PCIDAS6040,
+ BOARD_PCIDAS6052,
+ BOARD_PCIDAS6070,
+ BOARD_PCIDAS6071,
+ BOARD_PCIDAS4020_12,
+ BOARD_PCIDAS6402_16_JR,
+ BOARD_PCIDAS64_M1_16_JR,
+ BOARD_PCIDAS64_M2_16_JR,
+ BOARD_PCIDAS64_M3_16_JR,
+ BOARD_PCIDAS64_M1_14,
+ BOARD_PCIDAS64_M2_14,
+ BOARD_PCIDAS64_M3_14,
+};
+
+struct pcidas64_board {
+ const char *name;
+ int ai_se_chans; /* number of ai inputs in single-ended mode */
+ int ai_bits; /* analog input resolution */
+ int ai_speed; /* fastest conversion period in ns */
+ const struct comedi_lrange *ai_range_table;
+ const uint8_t *ai_range_code;
+ int ao_nchan; /* number of analog out channels */
+ int ao_bits; /* analog output resolution */
+ int ao_scan_speed; /* analog output scan speed */
+ const struct comedi_lrange *ao_range_table;
+ const int *ao_range_code;
+ const struct hw_fifo_info *const ai_fifo;
+ /* different board families have slightly different registers */
+ enum register_layout layout;
+ unsigned has_8255:1;
+};
+
+static const struct hw_fifo_info ai_fifo_4020 = {
+ .num_segments = 2,
+ .max_segment_length = 0x8000,
+ .sample_packing_ratio = 2,
+ .fifo_size_reg_mask = 0x7f,
+};
+
+static const struct hw_fifo_info ai_fifo_64xx = {
+ .num_segments = 4,
+ .max_segment_length = 0x800,
+ .sample_packing_ratio = 1,
+ .fifo_size_reg_mask = 0x3f,
+};
+
+static const struct hw_fifo_info ai_fifo_60xx = {
+ .num_segments = 4,
+ .max_segment_length = 0x800,
+ .sample_packing_ratio = 1,
+ .fifo_size_reg_mask = 0x7f,
+};
+
+/* maximum number of dma transfers we will chain together into a ring
+ * (and the maximum number of dma buffers we maintain) */
+#define MAX_AI_DMA_RING_COUNT (0x80000 / DMA_BUFFER_SIZE)
+#define MIN_AI_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE)
+#define AO_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE)
+static inline unsigned int ai_dma_ring_count(const struct pcidas64_board *board)
+{
+ if (board->layout == LAYOUT_4020)
+ return MAX_AI_DMA_RING_COUNT;
+
+ return MIN_AI_DMA_RING_COUNT;
+}
+
+static const int bytes_in_sample = 2;
+
+static const struct pcidas64_board pcidas64_boards[] = {
+ [BOARD_PCIDAS6402_16] = {
+ .name = "pci-das6402/16",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64xx,
+ .ai_range_code = ai_range_code_64xx,
+ .ao_range_table = &ao_ranges_64xx,
+ .ao_range_code = ao_range_code_64xx,
+ .ai_fifo = &ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS6402_12] = {
+ .name = "pci-das6402/12", /* XXX check */
+ .ai_se_chans = 64,
+ .ai_bits = 12,
+ .ai_speed = 5000,
+ .ao_nchan = 2,
+ .ao_bits = 12,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64xx,
+ .ai_range_code = ai_range_code_64xx,
+ .ao_range_table = &ao_ranges_64xx,
+ .ao_range_code = ao_range_code_64xx,
+ .ai_fifo = &ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M1_16] = {
+ .name = "pci-das64/m1/16",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 1000,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ao_range_table = &ao_ranges_64xx,
+ .ao_range_code = ao_range_code_64xx,
+ .ai_fifo = &ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M2_16] = {
+ .name = "pci-das64/m2/16",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 500,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ao_range_table = &ao_ranges_64xx,
+ .ao_range_code = ao_range_code_64xx,
+ .ai_fifo = &ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M3_16] = {
+ .name = "pci-das64/m3/16",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 333,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ao_range_table = &ao_ranges_64xx,
+ .ao_range_code = ao_range_code_64xx,
+ .ai_fifo = &ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS6013] = {
+ .name = "pci-das6013",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 0,
+ .ao_bits = 16,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_60xx,
+ .ai_range_code = ai_range_code_60xx,
+ .ao_range_table = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6014] = {
+ .name = "pci-das6014",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 100000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_60xx,
+ .ai_range_code = ai_range_code_60xx,
+ .ao_range_table = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6023] = {
+ .name = "pci-das6023",
+ .ai_se_chans = 16,
+ .ai_bits = 12,
+ .ai_speed = 5000,
+ .ao_nchan = 0,
+ .ao_scan_speed = 100000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_60xx,
+ .ai_range_code = ai_range_code_60xx,
+ .ao_range_table = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS6025] = {
+ .name = "pci-das6025",
+ .ai_se_chans = 16,
+ .ai_bits = 12,
+ .ai_speed = 5000,
+ .ao_nchan = 2,
+ .ao_bits = 12,
+ .ao_scan_speed = 100000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_60xx,
+ .ai_range_code = ai_range_code_60xx,
+ .ao_range_table = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS6030] = {
+ .name = "pci-das6030",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 10000,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6030,
+ .ai_range_code = ai_range_code_6030,
+ .ao_range_table = &ao_ranges_6030,
+ .ao_range_code = ao_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6031] = {
+ .name = "pci-das6031",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 10000,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6030,
+ .ai_range_code = ai_range_code_6030,
+ .ao_range_table = &ao_ranges_6030,
+ .ao_range_code = ao_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6032] = {
+ .name = "pci-das6032",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 10000,
+ .ao_nchan = 0,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6030,
+ .ai_range_code = ai_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6033] = {
+ .name = "pci-das6033",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 10000,
+ .ao_nchan = 0,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6030,
+ .ai_range_code = ai_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6034] = {
+ .name = "pci-das6034",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 0,
+ .ao_scan_speed = 0,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_60xx,
+ .ai_range_code = ai_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6035] = {
+ .name = "pci-das6035",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 2,
+ .ao_bits = 12,
+ .ao_scan_speed = 100000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_60xx,
+ .ai_range_code = ai_range_code_60xx,
+ .ao_range_table = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6036] = {
+ .name = "pci-das6036",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 100000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_60xx,
+ .ai_range_code = ai_range_code_60xx,
+ .ao_range_table = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6040] = {
+ .name = "pci-das6040",
+ .ai_se_chans = 16,
+ .ai_bits = 12,
+ .ai_speed = 2000,
+ .ao_nchan = 2,
+ .ao_bits = 12,
+ .ao_scan_speed = 1000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6052,
+ .ai_range_code = ai_range_code_6052,
+ .ao_range_table = &ao_ranges_6030,
+ .ao_range_code = ao_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6052] = {
+ .name = "pci-das6052",
+ .ai_se_chans = 16,
+ .ai_bits = 16,
+ .ai_speed = 3333,
+ .ao_nchan = 2,
+ .ao_bits = 16,
+ .ao_scan_speed = 3333,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6052,
+ .ai_range_code = ai_range_code_6052,
+ .ao_range_table = &ao_ranges_6030,
+ .ao_range_code = ao_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6070] = {
+ .name = "pci-das6070",
+ .ai_se_chans = 16,
+ .ai_bits = 12,
+ .ai_speed = 800,
+ .ao_nchan = 2,
+ .ao_bits = 12,
+ .ao_scan_speed = 1000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6052,
+ .ai_range_code = ai_range_code_6052,
+ .ao_range_table = &ao_ranges_6030,
+ .ao_range_code = ao_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6071] = {
+ .name = "pci-das6071",
+ .ai_se_chans = 64,
+ .ai_bits = 12,
+ .ai_speed = 800,
+ .ao_nchan = 2,
+ .ao_bits = 12,
+ .ao_scan_speed = 1000,
+ .layout = LAYOUT_60XX,
+ .ai_range_table = &ai_ranges_6052,
+ .ai_range_code = ai_range_code_6052,
+ .ao_range_table = &ao_ranges_6030,
+ .ao_range_code = ao_range_code_6030,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS4020_12] = {
+ .name = "pci-das4020/12",
+ .ai_se_chans = 4,
+ .ai_bits = 12,
+ .ai_speed = 50,
+ .ao_bits = 12,
+ .ao_nchan = 2,
+ .ao_scan_speed = 0, /* no hardware pacing on ao */
+ .layout = LAYOUT_4020,
+ .ai_range_table = &ai_ranges_4020,
+ .ao_range_table = &ao_ranges_4020,
+ .ao_range_code = ao_range_code_4020,
+ .ai_fifo = &ai_fifo_4020,
+ .has_8255 = 1,
+ },
+#if 0
+ /*
+ * The device id for these boards is unknown
+ */
+
+ [BOARD_PCIDAS6402_16_JR] = {
+ .name = "pci-das6402/16/jr",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .ao_nchan = 0,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64xx,
+ .ai_range_code = ai_range_code_64xx,
+ .ai_fifo = ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M1_16_JR] = {
+ .name = "pci-das64/m1/16/jr",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 1000,
+ .ao_nchan = 0,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ai_fifo = ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M2_16_JR] = {
+ .name = "pci-das64/m2/16/jr",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 500,
+ .ao_nchan = 0,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ai_fifo = ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M3_16_JR] = {
+ .name = "pci-das64/m3/16/jr",
+ .ai_se_chans = 64,
+ .ai_bits = 16,
+ .ai_speed = 333,
+ .ao_nchan = 0,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ai_fifo = ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M1_14] = {
+ .name = "pci-das64/m1/14",
+ .ai_se_chans = 64,
+ .ai_bits = 14,
+ .ai_speed = 1000,
+ .ao_nchan = 2,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ai_fifo = ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M2_14] = {
+ .name = "pci-das64/m2/14",
+ .ai_se_chans = 64,
+ .ai_bits = 14,
+ .ai_speed = 500,
+ .ao_nchan = 2,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ai_fifo = ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS64_M3_14] = {
+ .name = "pci-das64/m3/14",
+ .ai_se_chans = 64,
+ .ai_bits = 14,
+ .ai_speed = 333,
+ .ao_nchan = 2,
+ .ao_scan_speed = 10000,
+ .layout = LAYOUT_64XX,
+ .ai_range_table = &ai_ranges_64_mx,
+ .ai_range_code = ai_range_code_64_mx,
+ .ai_fifo = ai_fifo_64xx,
+ .has_8255 = 1,
+ },
+#endif
+};
+
+static inline unsigned short se_diff_bit_6xxx(struct comedi_device *dev,
+ int use_differential)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ if ((thisboard->layout == LAYOUT_64XX && !use_differential) ||
+ (thisboard->layout == LAYOUT_60XX && use_differential))
+ return ADC_SE_DIFF_BIT;
+
+ return 0;
+}
+
+struct ext_clock_info {
+ /* master clock divisor to use for scans with external master clock */
+ unsigned int divisor;
+ /* chanspec for master clock input when used as scan begin src */
+ unsigned int chanspec;
+};
+
+/* this structure is for data unique to this hardware driver. */
+struct pcidas64_private {
+ /* base addresses (physical) */
+ resource_size_t main_phys_iobase;
+ resource_size_t dio_counter_phys_iobase;
+ /* base addresses (ioremapped) */
+ void __iomem *plx9080_iobase;
+ void __iomem *main_iobase;
+ /* local address (used by dma controller) */
+ uint32_t local0_iobase;
+ uint32_t local1_iobase;
+ /* dma buffers for analog input */
+ uint16_t *ai_buffer[MAX_AI_DMA_RING_COUNT];
+ /* physical addresses of ai dma buffers */
+ dma_addr_t ai_buffer_bus_addr[MAX_AI_DMA_RING_COUNT];
+ /* array of ai dma descriptors read by plx9080,
+ * allocated to get proper alignment */
+ struct plx_dma_desc *ai_dma_desc;
+ /* physical address of ai dma descriptor array */
+ dma_addr_t ai_dma_desc_bus_addr;
+ /* index of the ai dma descriptor/buffer
+ * that is currently being used */
+ unsigned int ai_dma_index;
+ /* dma buffers for analog output */
+ uint16_t *ao_buffer[AO_DMA_RING_COUNT];
+ /* physical addresses of ao dma buffers */
+ dma_addr_t ao_buffer_bus_addr[AO_DMA_RING_COUNT];
+ struct plx_dma_desc *ao_dma_desc;
+ dma_addr_t ao_dma_desc_bus_addr;
+ /* keeps track of buffer where the next ao sample should go */
+ unsigned int ao_dma_index;
+ unsigned int hw_revision; /* stc chip hardware revision number */
+ /* last bits sent to INTR_ENABLE_REG register */
+ unsigned int intr_enable_bits;
+ /* last bits sent to ADC_CONTROL1_REG register */
+ uint16_t adc_control1_bits;
+ /* last bits sent to FIFO_SIZE_REG register */
+ uint16_t fifo_size_bits;
+ /* last bits sent to HW_CONFIG_REG register */
+ uint16_t hw_config_bits;
+ uint16_t dac_control1_bits;
+ /* last bits written to plx9080 control register */
+ uint32_t plx_control_bits;
+ /* last bits written to plx interrupt control and status register */
+ uint32_t plx_intcsr_bits;
+ /* index of calibration source readable through ai ch0 */
+ int calibration_source;
+ /* bits written to i2c calibration/range register */
+ uint8_t i2c_cal_range_bits;
+ /* configure digital triggers to trigger on falling edge */
+ unsigned int ext_trig_falling;
+ short ai_cmd_running;
+ unsigned int ai_fifo_segment_length;
+ struct ext_clock_info ext_clock;
+ unsigned short ao_bounce_buffer[DAC_FIFO_SIZE];
+};
+
+static unsigned int ai_range_bits_6xxx(const struct comedi_device *dev,
+ unsigned int range_index)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ return thisboard->ai_range_code[range_index] << 8;
+}
+
+static unsigned int hw_revision(const struct comedi_device *dev,
+ uint16_t hw_status_bits)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ if (thisboard->layout == LAYOUT_4020)
+ return (hw_status_bits >> 13) & 0x7;
+
+ return (hw_status_bits >> 12) & 0xf;
+}
+
+static void set_dac_range_bits(struct comedi_device *dev,
+ uint16_t *bits, unsigned int channel,
+ unsigned int range)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ unsigned int code = thisboard->ao_range_code[range];
+
+ if (channel > 1)
+ dev_err(dev->class_dev, "bug! bad channel?\n");
+ if (code & ~0x3)
+ dev_err(dev->class_dev, "bug! bad range code?\n");
+
+ *bits &= ~(0x3 << (2 * channel));
+ *bits |= code << (2 * channel);
+};
+
+static inline int ao_cmd_is_supported(const struct pcidas64_board *board)
+{
+ return board->ao_nchan && board->layout != LAYOUT_4020;
+}
+
+static void abort_dma(struct comedi_device *dev, unsigned int channel)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ /* spinlock for plx dma control/status reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ plx9080_abort_dma(devpriv->plx9080_iobase, channel);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static void disable_plx_interrupts(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ devpriv->plx_intcsr_bits = 0;
+ writel(devpriv->plx_intcsr_bits,
+ devpriv->plx9080_iobase + PLX_INTRCS_REG);
+}
+
+static void disable_ai_interrupts(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->intr_enable_bits &=
+ ~EN_ADC_INTR_SRC_BIT & ~EN_ADC_DONE_INTR_BIT &
+ ~EN_ADC_ACTIVE_INTR_BIT & ~EN_ADC_STOP_INTR_BIT &
+ ~EN_ADC_OVERRUN_BIT & ~ADC_INTR_SRC_MASK;
+ writew(devpriv->intr_enable_bits,
+ devpriv->main_iobase + INTR_ENABLE_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static void enable_ai_interrupts(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ uint32_t bits;
+ unsigned long flags;
+
+ bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT |
+ EN_ADC_ACTIVE_INTR_BIT | EN_ADC_STOP_INTR_BIT;
+ /* Use pio transfer and interrupt on end of conversion
+ * if CMDF_WAKE_EOS flag is set. */
+ if (cmd->flags & CMDF_WAKE_EOS) {
+ /* 4020 doesn't support pio transfers except for fifo dregs */
+ if (thisboard->layout != LAYOUT_4020)
+ bits |= ADC_INTR_EOSCAN_BITS | EN_ADC_INTR_SRC_BIT;
+ }
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->intr_enable_bits |= bits;
+ writew(devpriv->intr_enable_bits,
+ devpriv->main_iobase + INTR_ENABLE_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+/* initialize plx9080 chip */
+static void init_plx9080(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ uint32_t bits;
+ void __iomem *plx_iobase = devpriv->plx9080_iobase;
+
+ devpriv->plx_control_bits =
+ readl(devpriv->plx9080_iobase + PLX_CONTROL_REG);
+
+#ifdef __BIG_ENDIAN
+ bits = BIGEND_DMA0 | BIGEND_DMA1;
+#else
+ bits = 0;
+#endif
+ writel(bits, devpriv->plx9080_iobase + PLX_BIGEND_REG);
+
+ disable_plx_interrupts(dev);
+
+ abort_dma(dev, 0);
+ abort_dma(dev, 1);
+
+ /* configure dma0 mode */
+ bits = 0;
+ /* enable ready input, not sure if this is necessary */
+ bits |= PLX_DMA_EN_READYIN_BIT;
+ /* enable bterm, not sure if this is necessary */
+ bits |= PLX_EN_BTERM_BIT;
+ /* enable dma chaining */
+ bits |= PLX_EN_CHAIN_BIT;
+ /* enable interrupt on dma done
+ * (probably don't need this, since chain never finishes) */
+ bits |= PLX_EN_DMA_DONE_INTR_BIT;
+ /* don't increment local address during transfers
+ * (we are transferring from a fixed fifo register) */
+ bits |= PLX_LOCAL_ADDR_CONST_BIT;
+ /* route dma interrupt to pci bus */
+ bits |= PLX_DMA_INTR_PCI_BIT;
+ /* enable demand mode */
+ bits |= PLX_DEMAND_MODE_BIT;
+ /* enable local burst mode */
+ bits |= PLX_DMA_LOCAL_BURST_EN_BIT;
+ /* 4020 uses 32 bit dma */
+ if (thisboard->layout == LAYOUT_4020)
+ bits |= PLX_LOCAL_BUS_32_WIDE_BITS;
+ else /* localspace0 bus is 16 bits wide */
+ bits |= PLX_LOCAL_BUS_16_WIDE_BITS;
+ writel(bits, plx_iobase + PLX_DMA1_MODE_REG);
+ if (ao_cmd_is_supported(thisboard))
+ writel(bits, plx_iobase + PLX_DMA0_MODE_REG);
+
+ /* enable interrupts on plx 9080 */
+ devpriv->plx_intcsr_bits |=
+ ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE |
+ ICS_DMA0_E | ICS_DMA1_E;
+ writel(devpriv->plx_intcsr_bits,
+ devpriv->plx9080_iobase + PLX_INTRCS_REG);
+}
+
+static void disable_ai_pacing(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ disable_ai_interrupts(dev);
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->adc_control1_bits &= ~ADC_SW_GATE_BIT;
+ writew(devpriv->adc_control1_bits,
+ devpriv->main_iobase + ADC_CONTROL1_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* disable pacing, triggering, etc */
+ writew(ADC_DMA_DISABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT,
+ devpriv->main_iobase + ADC_CONTROL0_REG);
+}
+
+static int set_ai_fifo_segment_length(struct comedi_device *dev,
+ unsigned int num_entries)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ static const int increment_size = 0x100;
+ const struct hw_fifo_info *const fifo = thisboard->ai_fifo;
+ unsigned int num_increments;
+ uint16_t bits;
+
+ if (num_entries < increment_size)
+ num_entries = increment_size;
+ if (num_entries > fifo->max_segment_length)
+ num_entries = fifo->max_segment_length;
+
+ /* 1 == 256 entries, 2 == 512 entries, etc */
+ num_increments = (num_entries + increment_size / 2) / increment_size;
+
+ bits = (~(num_increments - 1)) & fifo->fifo_size_reg_mask;
+ devpriv->fifo_size_bits &= ~fifo->fifo_size_reg_mask;
+ devpriv->fifo_size_bits |= bits;
+ writew(devpriv->fifo_size_bits,
+ devpriv->main_iobase + FIFO_SIZE_REG);
+
+ devpriv->ai_fifo_segment_length = num_increments * increment_size;
+
+ return devpriv->ai_fifo_segment_length;
+}
+
+/* adjusts the size of hardware fifo (which determines block size for dma xfers) */
+static int set_ai_fifo_size(struct comedi_device *dev, unsigned int num_samples)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ unsigned int num_fifo_entries;
+ int retval;
+ const struct hw_fifo_info *const fifo = thisboard->ai_fifo;
+
+ num_fifo_entries = num_samples / fifo->sample_packing_ratio;
+
+ retval = set_ai_fifo_segment_length(dev,
+ num_fifo_entries /
+ fifo->num_segments);
+ if (retval < 0)
+ return retval;
+
+ num_samples = retval * fifo->num_segments * fifo->sample_packing_ratio;
+
+ return num_samples;
+}
+
+/* query length of fifo */
+static unsigned int ai_fifo_size(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+
+ return devpriv->ai_fifo_segment_length *
+ thisboard->ai_fifo->num_segments *
+ thisboard->ai_fifo->sample_packing_ratio;
+}
+
+static void init_stc_registers(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ uint16_t bits;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ /* bit should be set for 6025,
+ * although docs say boards with <= 16 chans should be cleared XXX */
+ if (1)
+ devpriv->adc_control1_bits |= ADC_QUEUE_CONFIG_BIT;
+ writew(devpriv->adc_control1_bits,
+ devpriv->main_iobase + ADC_CONTROL1_REG);
+
+ /* 6402/16 manual says this register must be initialized to 0xff? */
+ writew(0xff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
+
+ bits = SLOW_DAC_BIT | DMA_CH_SELECT_BIT;
+ if (thisboard->layout == LAYOUT_4020)
+ bits |= INTERNAL_CLOCK_4020_BITS;
+ devpriv->hw_config_bits |= bits;
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+
+ writew(0, devpriv->main_iobase + DAQ_SYNC_REG);
+ writew(0, devpriv->main_iobase + CALIBRATION_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* set fifos to maximum size */
+ devpriv->fifo_size_bits |= DAC_FIFO_BITS;
+ set_ai_fifo_segment_length(dev,
+ thisboard->ai_fifo->max_segment_length);
+
+ devpriv->dac_control1_bits = DAC_OUTPUT_ENABLE_BIT;
+ devpriv->intr_enable_bits =
+ /* EN_DAC_INTR_SRC_BIT | DAC_INTR_QEMPTY_BITS | */
+ EN_DAC_DONE_INTR_BIT | EN_DAC_UNDERRUN_BIT;
+ writew(devpriv->intr_enable_bits,
+ devpriv->main_iobase + INTR_ENABLE_REG);
+
+ disable_ai_pacing(dev);
+};
+
+static int alloc_and_init_dma_members(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ int i;
+
+ /* allocate pci dma buffers */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++) {
+ devpriv->ai_buffer[i] =
+ pci_alloc_consistent(pcidev, DMA_BUFFER_SIZE,
+ &devpriv->ai_buffer_bus_addr[i]);
+ if (!devpriv->ai_buffer[i])
+ return -ENOMEM;
+ }
+ for (i = 0; i < AO_DMA_RING_COUNT; i++) {
+ if (ao_cmd_is_supported(thisboard)) {
+ devpriv->ao_buffer[i] =
+ pci_alloc_consistent(pcidev, DMA_BUFFER_SIZE,
+ &devpriv->
+ ao_buffer_bus_addr[i]);
+ if (!devpriv->ao_buffer[i])
+ return -ENOMEM;
+ }
+ }
+ /* allocate dma descriptors */
+ devpriv->ai_dma_desc =
+ pci_alloc_consistent(pcidev, sizeof(struct plx_dma_desc) *
+ ai_dma_ring_count(thisboard),
+ &devpriv->ai_dma_desc_bus_addr);
+ if (!devpriv->ai_dma_desc)
+ return -ENOMEM;
+
+ if (ao_cmd_is_supported(thisboard)) {
+ devpriv->ao_dma_desc =
+ pci_alloc_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ AO_DMA_RING_COUNT,
+ &devpriv->ao_dma_desc_bus_addr);
+ if (!devpriv->ao_dma_desc)
+ return -ENOMEM;
+ }
+ /* initialize dma descriptors */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++) {
+ devpriv->ai_dma_desc[i].pci_start_addr =
+ cpu_to_le32(devpriv->ai_buffer_bus_addr[i]);
+ if (thisboard->layout == LAYOUT_4020)
+ devpriv->ai_dma_desc[i].local_start_addr =
+ cpu_to_le32(devpriv->local1_iobase +
+ ADC_FIFO_REG);
+ else
+ devpriv->ai_dma_desc[i].local_start_addr =
+ cpu_to_le32(devpriv->local0_iobase +
+ ADC_FIFO_REG);
+ devpriv->ai_dma_desc[i].transfer_size = cpu_to_le32(0);
+ devpriv->ai_dma_desc[i].next =
+ cpu_to_le32((devpriv->ai_dma_desc_bus_addr +
+ ((i + 1) % ai_dma_ring_count(thisboard)) *
+ sizeof(devpriv->ai_dma_desc[0])) |
+ PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT |
+ PLX_XFER_LOCAL_TO_PCI);
+ }
+ if (ao_cmd_is_supported(thisboard)) {
+ for (i = 0; i < AO_DMA_RING_COUNT; i++) {
+ devpriv->ao_dma_desc[i].pci_start_addr =
+ cpu_to_le32(devpriv->ao_buffer_bus_addr[i]);
+ devpriv->ao_dma_desc[i].local_start_addr =
+ cpu_to_le32(devpriv->local0_iobase +
+ DAC_FIFO_REG);
+ devpriv->ao_dma_desc[i].transfer_size = cpu_to_le32(0);
+ devpriv->ao_dma_desc[i].next =
+ cpu_to_le32((devpriv->ao_dma_desc_bus_addr +
+ ((i + 1) % (AO_DMA_RING_COUNT)) *
+ sizeof(devpriv->ao_dma_desc[0])) |
+ PLX_DESC_IN_PCI_BIT |
+ PLX_INTR_TERM_COUNT);
+ }
+ }
+ return 0;
+}
+
+static void cb_pcidas64_free_dma(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ int i;
+
+ if (!devpriv)
+ return;
+
+ /* free pci dma buffers */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++) {
+ if (devpriv->ai_buffer[i])
+ pci_free_consistent(pcidev,
+ DMA_BUFFER_SIZE,
+ devpriv->ai_buffer[i],
+ devpriv->ai_buffer_bus_addr[i]);
+ }
+ for (i = 0; i < AO_DMA_RING_COUNT; i++) {
+ if (devpriv->ao_buffer[i])
+ pci_free_consistent(pcidev,
+ DMA_BUFFER_SIZE,
+ devpriv->ao_buffer[i],
+ devpriv->ao_buffer_bus_addr[i]);
+ }
+ /* free dma descriptors */
+ if (devpriv->ai_dma_desc)
+ pci_free_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ ai_dma_ring_count(thisboard),
+ devpriv->ai_dma_desc,
+ devpriv->ai_dma_desc_bus_addr);
+ if (devpriv->ao_dma_desc)
+ pci_free_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ AO_DMA_RING_COUNT,
+ devpriv->ao_dma_desc,
+ devpriv->ao_dma_desc_bus_addr);
+}
+
+static inline void warn_external_queue(struct comedi_device *dev)
+{
+ dev_err(dev->class_dev,
+ "AO command and AI external channel queue cannot be used simultaneously\n");
+ dev_err(dev->class_dev,
+ "Use internal AI channel queue (channels must be consecutive and use same range/aref)\n");
+}
+
+/* Their i2c requires a huge delay on setting clock or data high for some reason */
+static const int i2c_high_udelay = 1000;
+static const int i2c_low_udelay = 10;
+
+/* set i2c data line high or low */
+static void i2c_set_sda(struct comedi_device *dev, int state)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ static const int data_bit = CTL_EE_W;
+ void __iomem *plx_control_addr = devpriv->plx9080_iobase +
+ PLX_CONTROL_REG;
+
+ if (state) {
+ /* set data line high */
+ devpriv->plx_control_bits &= ~data_bit;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(i2c_high_udelay);
+ } else { /* set data line low */
+
+ devpriv->plx_control_bits |= data_bit;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(i2c_low_udelay);
+ }
+}
+
+/* set i2c clock line high or low */
+static void i2c_set_scl(struct comedi_device *dev, int state)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ static const int clock_bit = CTL_USERO;
+ void __iomem *plx_control_addr = devpriv->plx9080_iobase +
+ PLX_CONTROL_REG;
+
+ if (state) {
+ /* set clock line high */
+ devpriv->plx_control_bits &= ~clock_bit;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(i2c_high_udelay);
+ } else { /* set clock line low */
+
+ devpriv->plx_control_bits |= clock_bit;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(i2c_low_udelay);
+ }
+}
+
+static void i2c_write_byte(struct comedi_device *dev, uint8_t byte)
+{
+ uint8_t bit;
+ unsigned int num_bits = 8;
+
+ for (bit = 1 << (num_bits - 1); bit; bit >>= 1) {
+ i2c_set_scl(dev, 0);
+ if ((byte & bit))
+ i2c_set_sda(dev, 1);
+ else
+ i2c_set_sda(dev, 0);
+ i2c_set_scl(dev, 1);
+ }
+}
+
+/* we can't really read the lines, so fake it */
+static int i2c_read_ack(struct comedi_device *dev)
+{
+ i2c_set_scl(dev, 0);
+ i2c_set_sda(dev, 1);
+ i2c_set_scl(dev, 1);
+
+ return 0; /* return fake acknowledge bit */
+}
+
+/* send start bit */
+static void i2c_start(struct comedi_device *dev)
+{
+ i2c_set_scl(dev, 1);
+ i2c_set_sda(dev, 1);
+ i2c_set_sda(dev, 0);
+}
+
+/* send stop bit */
+static void i2c_stop(struct comedi_device *dev)
+{
+ i2c_set_scl(dev, 0);
+ i2c_set_sda(dev, 0);
+ i2c_set_scl(dev, 1);
+ i2c_set_sda(dev, 1);
+}
+
+static void i2c_write(struct comedi_device *dev, unsigned int address,
+ const uint8_t *data, unsigned int length)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int i;
+ uint8_t bitstream;
+ static const int read_bit = 0x1;
+
+ /* XXX need mutex to prevent simultaneous attempts to access
+ * eeprom and i2c bus */
+
+ /* make sure we dont send anything to eeprom */
+ devpriv->plx_control_bits &= ~CTL_EE_CS;
+
+ i2c_stop(dev);
+ i2c_start(dev);
+
+ /* send address and write bit */
+ bitstream = (address << 1) & ~read_bit;
+ i2c_write_byte(dev, bitstream);
+
+ /* get acknowledge */
+ if (i2c_read_ack(dev) != 0) {
+ dev_err(dev->class_dev, "failed: no acknowledge\n");
+ i2c_stop(dev);
+ return;
+ }
+ /* write data bytes */
+ for (i = 0; i < length; i++) {
+ i2c_write_byte(dev, data[i]);
+ if (i2c_read_ack(dev) != 0) {
+ dev_err(dev->class_dev, "failed: no acknowledge\n");
+ i2c_stop(dev);
+ return;
+ }
+ }
+ i2c_stop(dev);
+}
+
+static int cb_pcidas64_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readw(devpriv->main_iobase + HW_STATUS_REG);
+ if (thisboard->layout == LAYOUT_4020) {
+ status = readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG);
+ if (status)
+ return 0;
+ } else {
+ if (pipe_full_bits(status))
+ return 0;
+ }
+ return -EBUSY;
+}
+
+static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int bits = 0, n;
+ unsigned int channel, range, aref;
+ unsigned long flags;
+ int ret;
+
+ channel = CR_CHAN(insn->chanspec);
+ range = CR_RANGE(insn->chanspec);
+ aref = CR_AREF(insn->chanspec);
+
+ /* disable card's analog input interrupt sources and pacing */
+ /* 4020 generates dac done interrupts even though they are disabled */
+ disable_ai_pacing(dev);
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (insn->chanspec & CR_ALT_FILTER)
+ devpriv->adc_control1_bits |= ADC_DITHER_BIT;
+ else
+ devpriv->adc_control1_bits &= ~ADC_DITHER_BIT;
+ writew(devpriv->adc_control1_bits,
+ devpriv->main_iobase + ADC_CONTROL1_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ if (thisboard->layout != LAYOUT_4020) {
+ /* use internal queue */
+ devpriv->hw_config_bits &= ~EXT_QUEUE_BIT;
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+
+ /* ALT_SOURCE is internal calibration reference */
+ if (insn->chanspec & CR_ALT_SOURCE) {
+ unsigned int cal_en_bit;
+
+ if (thisboard->layout == LAYOUT_60XX)
+ cal_en_bit = CAL_EN_60XX_BIT;
+ else
+ cal_en_bit = CAL_EN_64XX_BIT;
+ /* select internal reference source to connect
+ * to channel 0 */
+ writew(cal_en_bit |
+ adc_src_bits(devpriv->calibration_source),
+ devpriv->main_iobase + CALIBRATION_REG);
+ } else {
+ /* make sure internal calibration source
+ * is turned off */
+ writew(0, devpriv->main_iobase + CALIBRATION_REG);
+ }
+ /* load internal queue */
+ bits = 0;
+ /* set gain */
+ bits |= ai_range_bits_6xxx(dev, CR_RANGE(insn->chanspec));
+ /* set single-ended / differential */
+ bits |= se_diff_bit_6xxx(dev, aref == AREF_DIFF);
+ if (aref == AREF_COMMON)
+ bits |= ADC_COMMON_BIT;
+ bits |= adc_chan_bits(channel);
+ /* set stop channel */
+ writew(adc_chan_bits(channel),
+ devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
+ /* set start channel, and rest of settings */
+ writew(bits, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+ } else {
+ uint8_t old_cal_range_bits = devpriv->i2c_cal_range_bits;
+
+ devpriv->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
+ if (insn->chanspec & CR_ALT_SOURCE) {
+ devpriv->i2c_cal_range_bits |=
+ adc_src_4020_bits(devpriv->calibration_source);
+ } else { /* select BNC inputs */
+ devpriv->i2c_cal_range_bits |= adc_src_4020_bits(4);
+ }
+ /* select range */
+ if (range == 0)
+ devpriv->i2c_cal_range_bits |= attenuate_bit(channel);
+ else
+ devpriv->i2c_cal_range_bits &= ~attenuate_bit(channel);
+ /* update calibration/range i2c register only if necessary,
+ * as it is very slow */
+ if (old_cal_range_bits != devpriv->i2c_cal_range_bits) {
+ uint8_t i2c_data = devpriv->i2c_cal_range_bits;
+
+ i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data,
+ sizeof(i2c_data));
+ }
+
+ /* 4020 manual asks that sample interval register to be set
+ * before writing to convert register.
+ * Using somewhat arbitrary setting of 4 master clock ticks
+ * = 0.1 usec */
+ writew(0, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
+ writew(2, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ /* clear adc buffer (inside loop for 4020 sake) */
+ writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
+
+ /* trigger conversion, bits sent only matter for 4020 */
+ writew(adc_convert_chan_4020_bits(CR_CHAN(insn->chanspec)),
+ devpriv->main_iobase + ADC_CONVERT_REG);
+
+ /* wait for data */
+ ret = comedi_timeout(dev, s, insn, cb_pcidas64_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ if (thisboard->layout == LAYOUT_4020)
+ data[n] = readl(dev->mmio + ADC_FIFO_REG) & 0xffff;
+ else
+ data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG);
+ }
+
+ return n;
+}
+
+static int ai_config_calibration_source(struct comedi_device *dev,
+ unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int source = data[1];
+ int num_calibration_sources;
+
+ if (thisboard->layout == LAYOUT_60XX)
+ num_calibration_sources = 16;
+ else
+ num_calibration_sources = 8;
+ if (source >= num_calibration_sources) {
+ dev_dbg(dev->class_dev, "invalid calibration source: %i\n",
+ source);
+ return -EINVAL;
+ }
+
+ devpriv->calibration_source = source;
+
+ return 2;
+}
+
+static int ai_config_block_size(struct comedi_device *dev, unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ int fifo_size;
+ const struct hw_fifo_info *const fifo = thisboard->ai_fifo;
+ unsigned int block_size, requested_block_size;
+ int retval;
+
+ requested_block_size = data[1];
+
+ if (requested_block_size) {
+ fifo_size = requested_block_size * fifo->num_segments /
+ bytes_in_sample;
+
+ retval = set_ai_fifo_size(dev, fifo_size);
+ if (retval < 0)
+ return retval;
+ }
+
+ block_size = ai_fifo_size(dev) / fifo->num_segments * bytes_in_sample;
+
+ data[1] = block_size;
+
+ return 2;
+}
+
+static int ai_config_master_clock_4020(struct comedi_device *dev,
+ unsigned int *data)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int divisor = data[4];
+ int retval = 0;
+
+ if (divisor < 2) {
+ divisor = 2;
+ retval = -EAGAIN;
+ }
+
+ switch (data[1]) {
+ case COMEDI_EV_SCAN_BEGIN:
+ devpriv->ext_clock.divisor = divisor;
+ devpriv->ext_clock.chanspec = data[2];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ data[4] = divisor;
+
+ return retval ? retval : 5;
+}
+
+/* XXX could add support for 60xx series */
+static int ai_config_master_clock(struct comedi_device *dev, unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ switch (thisboard->layout) {
+ case LAYOUT_4020:
+ return ai_config_master_clock_4020(dev, data);
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int id = data[0];
+
+ switch (id) {
+ case INSN_CONFIG_ALT_SOURCE:
+ return ai_config_calibration_source(dev, data);
+ case INSN_CONFIG_BLOCK_SIZE:
+ return ai_config_block_size(dev, data);
+ case INSN_CONFIG_TIMER_1:
+ return ai_config_master_clock(dev, data);
+ default:
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+/* Gets nearest achievable timing given master clock speed, does not
+ * take into account possible minimum/maximum divisor values. Used
+ * by other timing checking functions. */
+static unsigned int get_divisor(unsigned int ns, unsigned int flags)
+{
+ unsigned int divisor;
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_UP:
+ divisor = (ns + TIMER_BASE - 1) / TIMER_BASE;
+ break;
+ case CMDF_ROUND_DOWN:
+ divisor = ns / TIMER_BASE;
+ break;
+ case CMDF_ROUND_NEAREST:
+ default:
+ divisor = (ns + TIMER_BASE / 2) / TIMER_BASE;
+ break;
+ }
+ return divisor;
+}
+
+/* utility function that rounds desired timing to an achievable time, and
+ * sets cmd members appropriately.
+ * adc paces conversions from master clock by dividing by (x + 3) where x is 24 bit number
+ */
+static void check_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ unsigned long long convert_divisor = 0;
+ unsigned int scan_divisor;
+ static const int min_convert_divisor = 3;
+ static const int max_convert_divisor =
+ max_counter_value + min_convert_divisor;
+ static const int min_scan_divisor_4020 = 2;
+ unsigned long long max_scan_divisor, min_scan_divisor;
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ if (thisboard->layout == LAYOUT_4020) {
+ cmd->convert_arg = 0;
+ } else {
+ convert_divisor = get_divisor(cmd->convert_arg,
+ cmd->flags);
+ if (convert_divisor > max_convert_divisor)
+ convert_divisor = max_convert_divisor;
+ if (convert_divisor < min_convert_divisor)
+ convert_divisor = min_convert_divisor;
+ cmd->convert_arg = convert_divisor * TIMER_BASE;
+ }
+ } else if (cmd->convert_src == TRIG_NOW) {
+ cmd->convert_arg = 0;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags);
+ if (cmd->convert_src == TRIG_TIMER) {
+ min_scan_divisor = convert_divisor * cmd->chanlist_len;
+ max_scan_divisor =
+ (convert_divisor * cmd->chanlist_len - 1) +
+ max_counter_value;
+ } else {
+ min_scan_divisor = min_scan_divisor_4020;
+ max_scan_divisor = max_counter_value + min_scan_divisor;
+ }
+ if (scan_divisor > max_scan_divisor)
+ scan_divisor = max_scan_divisor;
+ if (scan_divisor < min_scan_divisor)
+ scan_divisor = min_scan_divisor;
+ cmd->scan_begin_arg = scan_divisor * TIMER_BASE;
+ }
+}
+
+static int cb_pcidas64_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *board = dev->board_ptr;
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "all elements in chanlist must use the same analog reference\n");
+ return -EINVAL;
+ }
+ }
+
+ if (board->layout == LAYOUT_4020) {
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i)) {
+ dev_dbg(dev->class_dev,
+ "chanlist must use consecutive channels\n");
+ return -EINVAL;
+ }
+ }
+ if (cmd->chanlist_len == 3) {
+ dev_dbg(dev->class_dev,
+ "chanlist cannot be 3 channels long, use 1, 2, or 4 channels\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ int err = 0;
+ unsigned int tmp_arg, tmp_arg2;
+ unsigned int triggers;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+
+ triggers = TRIG_TIMER;
+ if (thisboard->layout == LAYOUT_4020)
+ triggers |= TRIG_OTHER;
+ else
+ triggers |= TRIG_FOLLOW;
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, triggers);
+
+ triggers = TRIG_TIMER;
+ if (thisboard->layout == LAYOUT_4020)
+ triggers |= TRIG_NOW;
+ else
+ triggers |= TRIG_EXT;
+ err |= comedi_check_trigger_src(&cmd->convert_src, triggers);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_EXT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /*
+ * start_arg is the CR_CHAN | CR_INVERT of the
+ * external trigger.
+ */
+ break;
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ if (thisboard->layout == LAYOUT_4020) {
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg,
+ 0);
+ } else {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->
+ ai_speed);
+ /*
+ * if scans are timed faster than conversion rate
+ * allows
+ */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(
+ &cmd->scan_begin_arg,
+ cmd->convert_arg *
+ cmd->chanlist_len);
+ }
+ }
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_EXT:
+ break;
+ case TRIG_COUNT:
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ default:
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ tmp_arg = cmd->convert_arg;
+ tmp_arg2 = cmd->scan_begin_arg;
+ check_adc_timing(dev, cmd);
+ if (tmp_arg != cmd->convert_arg)
+ err++;
+ if (tmp_arg2 != cmd->scan_begin_arg)
+ err++;
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas64_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int use_hw_sample_counter(struct comedi_cmd *cmd)
+{
+/* disable for now until I work out a race */
+ return 0;
+
+ if (cmd->stop_src == TRIG_COUNT && cmd->stop_arg <= max_counter_value)
+ return 1;
+
+ return 0;
+}
+
+static void setup_sample_counters(struct comedi_device *dev,
+ struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ /* load hardware conversion counter */
+ if (use_hw_sample_counter(cmd)) {
+ writew(cmd->stop_arg & 0xffff,
+ devpriv->main_iobase + ADC_COUNT_LOWER_REG);
+ writew((cmd->stop_arg >> 16) & 0xff,
+ devpriv->main_iobase + ADC_COUNT_UPPER_REG);
+ } else {
+ writew(1, devpriv->main_iobase + ADC_COUNT_LOWER_REG);
+ }
+}
+
+static inline unsigned int dma_transfer_size(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int num_samples;
+
+ num_samples = devpriv->ai_fifo_segment_length *
+ thisboard->ai_fifo->sample_packing_ratio;
+ if (num_samples > DMA_BUFFER_SIZE / sizeof(uint16_t))
+ num_samples = DMA_BUFFER_SIZE / sizeof(uint16_t);
+
+ return num_samples;
+}
+
+static uint32_t ai_convert_counter_6xxx(const struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ /* supposed to load counter with desired divisor minus 3 */
+ return cmd->convert_arg / TIMER_BASE - 3;
+}
+
+static uint32_t ai_scan_counter_6xxx(struct comedi_device *dev,
+ struct comedi_cmd *cmd)
+{
+ uint32_t count;
+
+ /* figure out how long we need to delay at end of scan */
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ count = (cmd->scan_begin_arg -
+ (cmd->convert_arg * (cmd->chanlist_len - 1))) /
+ TIMER_BASE;
+ break;
+ case TRIG_FOLLOW:
+ count = cmd->convert_arg / TIMER_BASE;
+ break;
+ default:
+ return 0;
+ }
+ return count - 3;
+}
+
+static uint32_t ai_convert_counter_4020(struct comedi_device *dev,
+ struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int divisor;
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ divisor = cmd->scan_begin_arg / TIMER_BASE;
+ break;
+ case TRIG_OTHER:
+ divisor = devpriv->ext_clock.divisor;
+ break;
+ default: /* should never happen */
+ dev_err(dev->class_dev, "bug! failed to set ai pacing!\n");
+ divisor = 1000;
+ break;
+ }
+
+ /* supposed to load counter with desired divisor minus 2 for 4020 */
+ return divisor - 2;
+}
+
+static void select_master_clock_4020(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ /* select internal/external master clock */
+ devpriv->hw_config_bits &= ~MASTER_CLOCK_4020_MASK;
+ if (cmd->scan_begin_src == TRIG_OTHER) {
+ int chanspec = devpriv->ext_clock.chanspec;
+
+ if (CR_CHAN(chanspec))
+ devpriv->hw_config_bits |= BNC_CLOCK_4020_BITS;
+ else
+ devpriv->hw_config_bits |= EXT_CLOCK_4020_BITS;
+ } else {
+ devpriv->hw_config_bits |= INTERNAL_CLOCK_4020_BITS;
+ }
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+}
+
+static void select_master_clock(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ switch (thisboard->layout) {
+ case LAYOUT_4020:
+ select_master_clock_4020(dev, cmd);
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void dma_start_sync(struct comedi_device *dev,
+ unsigned int channel)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ /* spinlock for plx dma control/status reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (channel)
+ writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT |
+ PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_iobase + PLX_DMA1_CS_REG);
+ else
+ writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT |
+ PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static void set_ai_pacing(struct comedi_device *dev, struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ uint32_t convert_counter = 0, scan_counter = 0;
+
+ check_adc_timing(dev, cmd);
+
+ select_master_clock(dev, cmd);
+
+ if (thisboard->layout == LAYOUT_4020) {
+ convert_counter = ai_convert_counter_4020(dev, cmd);
+ } else {
+ convert_counter = ai_convert_counter_6xxx(dev, cmd);
+ scan_counter = ai_scan_counter_6xxx(dev, cmd);
+ }
+
+ /* load lower 16 bits of convert interval */
+ writew(convert_counter & 0xffff,
+ devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
+ /* load upper 8 bits of convert interval */
+ writew((convert_counter >> 16) & 0xff,
+ devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
+ /* load lower 16 bits of scan delay */
+ writew(scan_counter & 0xffff,
+ devpriv->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);
+ /* load upper 8 bits of scan delay */
+ writew((scan_counter >> 16) & 0xff,
+ devpriv->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);
+}
+
+static int use_internal_queue_6xxx(const struct comedi_cmd *cmd)
+{
+ int i;
+
+ for (i = 0; i + 1 < cmd->chanlist_len; i++) {
+ if (CR_CHAN(cmd->chanlist[i + 1]) !=
+ CR_CHAN(cmd->chanlist[i]) + 1)
+ return 0;
+ if (CR_RANGE(cmd->chanlist[i + 1]) !=
+ CR_RANGE(cmd->chanlist[i]))
+ return 0;
+ if (CR_AREF(cmd->chanlist[i + 1]) != CR_AREF(cmd->chanlist[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static int setup_channel_queue(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned short bits;
+ int i;
+
+ if (thisboard->layout != LAYOUT_4020) {
+ if (use_internal_queue_6xxx(cmd)) {
+ devpriv->hw_config_bits &= ~EXT_QUEUE_BIT;
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+ bits = 0;
+ /* set channel */
+ bits |= adc_chan_bits(CR_CHAN(cmd->chanlist[0]));
+ /* set gain */
+ bits |= ai_range_bits_6xxx(dev,
+ CR_RANGE(cmd->chanlist[0]));
+ /* set single-ended / differential */
+ bits |= se_diff_bit_6xxx(dev,
+ CR_AREF(cmd->chanlist[0]) ==
+ AREF_DIFF);
+ if (CR_AREF(cmd->chanlist[0]) == AREF_COMMON)
+ bits |= ADC_COMMON_BIT;
+ /* set stop channel */
+ writew(adc_chan_bits
+ (CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])),
+ devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
+ /* set start channel, and rest of settings */
+ writew(bits,
+ devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+ } else {
+ /* use external queue */
+ if (dev->write_subdev && dev->write_subdev->busy) {
+ warn_external_queue(dev);
+ return -EBUSY;
+ }
+ devpriv->hw_config_bits |= EXT_QUEUE_BIT;
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+ /* clear DAC buffer to prevent weird interactions */
+ writew(0,
+ devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);
+ /* clear queue pointer */
+ writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
+ /* load external queue */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ bits = 0;
+ /* set channel */
+ bits |= adc_chan_bits(CR_CHAN(cmd->
+ chanlist[i]));
+ /* set gain */
+ bits |= ai_range_bits_6xxx(dev,
+ CR_RANGE(cmd->
+ chanlist
+ [i]));
+ /* set single-ended / differential */
+ bits |= se_diff_bit_6xxx(dev,
+ CR_AREF(cmd->
+ chanlist[i]) ==
+ AREF_DIFF);
+ if (CR_AREF(cmd->chanlist[i]) == AREF_COMMON)
+ bits |= ADC_COMMON_BIT;
+ /* mark end of queue */
+ if (i == cmd->chanlist_len - 1)
+ bits |= QUEUE_EOSCAN_BIT |
+ QUEUE_EOSEQ_BIT;
+ writew(bits,
+ devpriv->main_iobase +
+ ADC_QUEUE_FIFO_REG);
+ }
+ /* doing a queue clear is not specified in board docs,
+ * but required for reliable operation */
+ writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
+ /* prime queue holding register */
+ writew(0, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+ }
+ } else {
+ unsigned short old_cal_range_bits = devpriv->i2c_cal_range_bits;
+
+ devpriv->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
+ /* select BNC inputs */
+ devpriv->i2c_cal_range_bits |= adc_src_4020_bits(4);
+ /* select ranges */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int channel = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (range == 0)
+ devpriv->i2c_cal_range_bits |=
+ attenuate_bit(channel);
+ else
+ devpriv->i2c_cal_range_bits &=
+ ~attenuate_bit(channel);
+ }
+ /* update calibration/range i2c register only if necessary,
+ * as it is very slow */
+ if (old_cal_range_bits != devpriv->i2c_cal_range_bits) {
+ uint8_t i2c_data = devpriv->i2c_cal_range_bits;
+
+ i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data,
+ sizeof(i2c_data));
+ }
+ }
+ return 0;
+}
+
+static inline void load_first_dma_descriptor(struct comedi_device *dev,
+ unsigned int dma_channel,
+ unsigned int descriptor_bits)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ /* The transfer size, pci address, and local address registers
+ * are supposedly unused during chained dma,
+ * but I have found that left over values from last operation
+ * occasionally cause problems with transfer of first dma
+ * block. Initializing them to zero seems to fix the problem. */
+ if (dma_channel) {
+ writel(0,
+ devpriv->plx9080_iobase + PLX_DMA1_TRANSFER_SIZE_REG);
+ writel(0, devpriv->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG);
+ writel(0,
+ devpriv->plx9080_iobase + PLX_DMA1_LOCAL_ADDRESS_REG);
+ writel(descriptor_bits,
+ devpriv->plx9080_iobase + PLX_DMA1_DESCRIPTOR_REG);
+ } else {
+ writel(0,
+ devpriv->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG);
+ writel(0, devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG);
+ writel(0,
+ devpriv->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG);
+ writel(descriptor_bits,
+ devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG);
+ }
+}
+
+static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ uint32_t bits;
+ unsigned int i;
+ unsigned long flags;
+ int retval;
+
+ disable_ai_pacing(dev);
+ abort_dma(dev, 1);
+
+ retval = setup_channel_queue(dev, cmd);
+ if (retval < 0)
+ return retval;
+
+ /* make sure internal calibration source is turned off */
+ writew(0, devpriv->main_iobase + CALIBRATION_REG);
+
+ set_ai_pacing(dev, cmd);
+
+ setup_sample_counters(dev, cmd);
+
+ enable_ai_interrupts(dev, cmd);
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ /* set mode, allow conversions through software gate */
+ devpriv->adc_control1_bits |= ADC_SW_GATE_BIT;
+ devpriv->adc_control1_bits &= ~ADC_DITHER_BIT;
+ if (thisboard->layout != LAYOUT_4020) {
+ devpriv->adc_control1_bits &= ~ADC_MODE_MASK;
+ if (cmd->convert_src == TRIG_EXT)
+ /* good old mode 13 */
+ devpriv->adc_control1_bits |= adc_mode_bits(13);
+ else
+ /* mode 8. What else could you need? */
+ devpriv->adc_control1_bits |= adc_mode_bits(8);
+ } else {
+ devpriv->adc_control1_bits &= ~CHANNEL_MODE_4020_MASK;
+ if (cmd->chanlist_len == 4)
+ devpriv->adc_control1_bits |= FOUR_CHANNEL_4020_BITS;
+ else if (cmd->chanlist_len == 2)
+ devpriv->adc_control1_bits |= TWO_CHANNEL_4020_BITS;
+ devpriv->adc_control1_bits &= ~ADC_LO_CHANNEL_4020_MASK;
+ devpriv->adc_control1_bits |=
+ adc_lo_chan_4020_bits(CR_CHAN(cmd->chanlist[0]));
+ devpriv->adc_control1_bits &= ~ADC_HI_CHANNEL_4020_MASK;
+ devpriv->adc_control1_bits |=
+ adc_hi_chan_4020_bits(CR_CHAN(cmd->chanlist
+ [cmd->chanlist_len - 1]));
+ }
+ writew(devpriv->adc_control1_bits,
+ devpriv->main_iobase + ADC_CONTROL1_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* clear adc buffer */
+ writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
+
+ if ((cmd->flags & CMDF_WAKE_EOS) == 0 ||
+ thisboard->layout == LAYOUT_4020) {
+ devpriv->ai_dma_index = 0;
+
+ /* set dma transfer size */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++)
+ devpriv->ai_dma_desc[i].transfer_size =
+ cpu_to_le32(dma_transfer_size(dev) *
+ sizeof(uint16_t));
+
+ /* give location of first dma descriptor */
+ load_first_dma_descriptor(dev, 1,
+ devpriv->ai_dma_desc_bus_addr |
+ PLX_DESC_IN_PCI_BIT |
+ PLX_INTR_TERM_COUNT |
+ PLX_XFER_LOCAL_TO_PCI);
+
+ dma_start_sync(dev, 1);
+ }
+
+ if (thisboard->layout == LAYOUT_4020) {
+ /* set source for external triggers */
+ bits = 0;
+ if (cmd->start_src == TRIG_EXT && CR_CHAN(cmd->start_arg))
+ bits |= EXT_START_TRIG_BNC_BIT;
+ if (cmd->stop_src == TRIG_EXT && CR_CHAN(cmd->stop_arg))
+ bits |= EXT_STOP_TRIG_BNC_BIT;
+ writew(bits, devpriv->main_iobase + DAQ_ATRIG_LOW_4020_REG);
+ }
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ /* enable pacing, triggering, etc */
+ bits = ADC_ENABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT;
+ if (cmd->flags & CMDF_WAKE_EOS)
+ bits |= ADC_DMA_DISABLE_BIT;
+ /* set start trigger */
+ if (cmd->start_src == TRIG_EXT) {
+ bits |= ADC_START_TRIG_EXT_BITS;
+ if (cmd->start_arg & CR_INVERT)
+ bits |= ADC_START_TRIG_FALLING_BIT;
+ } else if (cmd->start_src == TRIG_NOW) {
+ bits |= ADC_START_TRIG_SOFT_BITS;
+ }
+ if (use_hw_sample_counter(cmd))
+ bits |= ADC_SAMPLE_COUNTER_EN_BIT;
+ writew(bits, devpriv->main_iobase + ADC_CONTROL0_REG);
+
+ devpriv->ai_cmd_running = 1;
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* start acquisition */
+ if (cmd->start_src == TRIG_NOW)
+ writew(0, devpriv->main_iobase + ADC_START_REG);
+
+ return 0;
+}
+
+/* read num_samples from 16 bit wide ai fifo */
+static void pio_drain_ai_fifo_16(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int i;
+ uint16_t prepost_bits;
+ int read_segment, read_index, write_segment, write_index;
+ int num_samples;
+
+ do {
+ /* get least significant 15 bits */
+ read_index = readw(devpriv->main_iobase + ADC_READ_PNTR_REG) &
+ 0x7fff;
+ write_index = readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG) &
+ 0x7fff;
+ /* Get most significant bits (grey code).
+ * Different boards use different code so use a scheme
+ * that doesn't depend on encoding. This read must
+ * occur after reading least significant 15 bits to avoid race
+ * with fifo switching to next segment. */
+ prepost_bits = readw(devpriv->main_iobase + PREPOST_REG);
+
+ /* if read and write pointers are not on the same fifo segment,
+ * read to the end of the read segment */
+ read_segment = adc_upper_read_ptr_code(prepost_bits);
+ write_segment = adc_upper_write_ptr_code(prepost_bits);
+
+ if (read_segment != write_segment)
+ num_samples =
+ devpriv->ai_fifo_segment_length - read_index;
+ else
+ num_samples = write_index - read_index;
+ if (num_samples < 0) {
+ dev_err(dev->class_dev,
+ "cb_pcidas64: bug! num_samples < 0\n");
+ break;
+ }
+
+ num_samples = comedi_nsamples_left(s, num_samples);
+ if (num_samples == 0)
+ break;
+
+ for (i = 0; i < num_samples; i++) {
+ unsigned short val;
+
+ val = readw(devpriv->main_iobase + ADC_FIFO_REG);
+ comedi_buf_write_samples(s, &val, 1);
+ }
+
+ } while (read_segment != write_segment);
+}
+
+/* Read from 32 bit wide ai fifo of 4020 - deal with insane grey coding of
+ * pointers. The pci-4020 hardware only supports dma transfers (it only
+ * supports the use of pio for draining the last remaining points from the
+ * fifo when a data acquisition operation has completed).
+ */
+static void pio_drain_ai_fifo_32(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int nsamples;
+ unsigned int i;
+ uint32_t fifo_data;
+ int write_code =
+ readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff;
+ int read_code =
+ readw(devpriv->main_iobase + ADC_READ_PNTR_REG) & 0x7fff;
+
+ nsamples = comedi_nsamples_left(s, 100000);
+ for (i = 0; read_code != write_code && i < nsamples;) {
+ unsigned short val;
+
+ fifo_data = readl(dev->mmio + ADC_FIFO_REG);
+ val = fifo_data & 0xffff;
+ comedi_buf_write_samples(s, &val, 1);
+ i++;
+ if (i < nsamples) {
+ val = (fifo_data >> 16) & 0xffff;
+ comedi_buf_write_samples(s, &val, 1);
+ i++;
+ }
+ read_code = readw(devpriv->main_iobase + ADC_READ_PNTR_REG) &
+ 0x7fff;
+ }
+}
+
+/* empty fifo */
+static void pio_drain_ai_fifo(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ if (thisboard->layout == LAYOUT_4020)
+ pio_drain_ai_fifo_32(dev);
+ else
+ pio_drain_ai_fifo_16(dev);
+}
+
+static void drain_dma_buffers(struct comedi_device *dev, unsigned int channel)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ uint32_t next_transfer_addr;
+ int j;
+ int num_samples = 0;
+ void __iomem *pci_addr_reg;
+
+ if (channel)
+ pci_addr_reg =
+ devpriv->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG;
+ else
+ pci_addr_reg =
+ devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG;
+
+ /* loop until we have read all the full buffers */
+ for (j = 0, next_transfer_addr = readl(pci_addr_reg);
+ (next_transfer_addr <
+ devpriv->ai_buffer_bus_addr[devpriv->ai_dma_index] ||
+ next_transfer_addr >=
+ devpriv->ai_buffer_bus_addr[devpriv->ai_dma_index] +
+ DMA_BUFFER_SIZE) && j < ai_dma_ring_count(thisboard); j++) {
+ /* transfer data from dma buffer to comedi buffer */
+ num_samples = comedi_nsamples_left(s, dma_transfer_size(dev));
+ comedi_buf_write_samples(s,
+ devpriv->ai_buffer[devpriv->ai_dma_index],
+ num_samples);
+ devpriv->ai_dma_index = (devpriv->ai_dma_index + 1) %
+ ai_dma_ring_count(thisboard);
+ }
+ /* XXX check for dma ring buffer overrun
+ * (use end-of-chain bit to mark last unused buffer) */
+}
+
+static void handle_ai_interrupt(struct comedi_device *dev,
+ unsigned short status,
+ unsigned int plx_status)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ uint8_t dma1_status;
+ unsigned long flags;
+
+ /* check for fifo overrun */
+ if (status & ADC_OVERRUN_BIT) {
+ dev_err(dev->class_dev, "fifo overrun\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+ /* spin lock makes sure no one else changes plx dma control reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ dma1_status = readb(devpriv->plx9080_iobase + PLX_DMA1_CS_REG);
+ if (plx_status & ICS_DMA1_A) { /* dma chan 1 interrupt */
+ writeb((dma1_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_iobase + PLX_DMA1_CS_REG);
+
+ if (dma1_status & PLX_DMA_EN_BIT)
+ drain_dma_buffers(dev, 1);
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* drain fifo with pio */
+ if ((status & ADC_DONE_BIT) ||
+ ((cmd->flags & CMDF_WAKE_EOS) &&
+ (status & ADC_INTR_PENDING_BIT) &&
+ (thisboard->layout != LAYOUT_4020))) {
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (devpriv->ai_cmd_running) {
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ pio_drain_ai_fifo(dev);
+ } else {
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ }
+ }
+ /* if we are have all the data, then quit */
+ if ((cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) ||
+ (cmd->stop_src == TRIG_EXT && (status & ADC_STOP_BIT)))
+ async->events |= COMEDI_CB_EOA;
+
+ comedi_handle_events(dev, s);
+}
+
+static inline unsigned int prev_ao_dma_index(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int buffer_index;
+
+ if (devpriv->ao_dma_index == 0)
+ buffer_index = AO_DMA_RING_COUNT - 1;
+ else
+ buffer_index = devpriv->ao_dma_index - 1;
+ return buffer_index;
+}
+
+static int last_ao_dma_load_completed(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int buffer_index;
+ unsigned int transfer_address;
+ unsigned short dma_status;
+
+ buffer_index = prev_ao_dma_index(dev);
+ dma_status = readb(devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ if ((dma_status & PLX_DMA_DONE_BIT) == 0)
+ return 0;
+
+ transfer_address =
+ readl(devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG);
+ if (transfer_address != devpriv->ao_buffer_bus_addr[buffer_index])
+ return 0;
+
+ return 1;
+}
+
+static inline int ao_dma_needs_restart(struct comedi_device *dev,
+ unsigned short dma_status)
+{
+ if ((dma_status & PLX_DMA_DONE_BIT) == 0 ||
+ (dma_status & PLX_DMA_EN_BIT) == 0)
+ return 0;
+ if (last_ao_dma_load_completed(dev))
+ return 0;
+
+ return 1;
+}
+
+static void restart_ao_dma(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int dma_desc_bits;
+
+ dma_desc_bits =
+ readl(devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG);
+ dma_desc_bits &= ~PLX_END_OF_CHAIN_BIT;
+ load_first_dma_descriptor(dev, 0, dma_desc_bits);
+
+ dma_start_sync(dev, 0);
+}
+
+static unsigned int cb_pcidas64_ao_fill_buffer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *dest,
+ unsigned int max_bytes)
+{
+ unsigned int nsamples = comedi_bytes_to_samples(s, max_bytes);
+ unsigned int actual_bytes;
+
+ nsamples = comedi_nsamples_left(s, nsamples);
+ actual_bytes = comedi_buf_read_samples(s, dest, nsamples);
+
+ return comedi_bytes_to_samples(s, actual_bytes);
+}
+
+static unsigned int load_ao_dma_buffer(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ unsigned int buffer_index = devpriv->ao_dma_index;
+ unsigned int prev_buffer_index = prev_ao_dma_index(dev);
+ unsigned int nsamples;
+ unsigned int nbytes;
+ unsigned int next_bits;
+
+ nsamples = cb_pcidas64_ao_fill_buffer(dev, s,
+ devpriv->ao_buffer[buffer_index],
+ DMA_BUFFER_SIZE);
+ if (nsamples == 0)
+ return 0;
+
+ nbytes = comedi_samples_to_bytes(s, nsamples);
+ devpriv->ao_dma_desc[buffer_index].transfer_size = cpu_to_le32(nbytes);
+ /* set end of chain bit so we catch underruns */
+ next_bits = le32_to_cpu(devpriv->ao_dma_desc[buffer_index].next);
+ next_bits |= PLX_END_OF_CHAIN_BIT;
+ devpriv->ao_dma_desc[buffer_index].next = cpu_to_le32(next_bits);
+ /* clear end of chain bit on previous buffer now that we have set it
+ * for the last buffer */
+ next_bits = le32_to_cpu(devpriv->ao_dma_desc[prev_buffer_index].next);
+ next_bits &= ~PLX_END_OF_CHAIN_BIT;
+ devpriv->ao_dma_desc[prev_buffer_index].next = cpu_to_le32(next_bits);
+
+ devpriv->ao_dma_index = (buffer_index + 1) % AO_DMA_RING_COUNT;
+
+ return nbytes;
+}
+
+static void load_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int num_bytes;
+ unsigned int next_transfer_addr;
+ void __iomem *pci_addr_reg =
+ devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG;
+ unsigned int buffer_index;
+
+ do {
+ buffer_index = devpriv->ao_dma_index;
+ /* don't overwrite data that hasn't been transferred yet */
+ next_transfer_addr = readl(pci_addr_reg);
+ if (next_transfer_addr >=
+ devpriv->ao_buffer_bus_addr[buffer_index] &&
+ next_transfer_addr <
+ devpriv->ao_buffer_bus_addr[buffer_index] +
+ DMA_BUFFER_SIZE)
+ return;
+ num_bytes = load_ao_dma_buffer(dev, cmd);
+ } while (num_bytes >= DMA_BUFFER_SIZE);
+}
+
+static void handle_ao_interrupt(struct comedi_device *dev,
+ unsigned short status, unsigned int plx_status)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ uint8_t dma0_status;
+ unsigned long flags;
+
+ /* board might not support ao, in which case write_subdev is NULL */
+ if (!s)
+ return;
+ async = s->async;
+ cmd = &async->cmd;
+
+ /* spin lock makes sure no one else changes plx dma control reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ dma0_status = readb(devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ if (plx_status & ICS_DMA0_A) { /* dma chan 0 interrupt */
+ if ((dma0_status & PLX_DMA_EN_BIT) &&
+ !(dma0_status & PLX_DMA_DONE_BIT))
+ writeb(PLX_DMA_EN_BIT | PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ else
+ writeb(PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ if (dma0_status & PLX_DMA_EN_BIT) {
+ load_ao_dma(dev, cmd);
+ /* try to recover from dma end-of-chain event */
+ if (ao_dma_needs_restart(dev, dma0_status))
+ restart_ao_dma(dev);
+ }
+ } else {
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ }
+
+ if ((status & DAC_DONE_BIT)) {
+ if ((cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) ||
+ last_ao_dma_load_completed(dev))
+ async->events |= COMEDI_CB_EOA;
+ else
+ async->events |= COMEDI_CB_ERROR;
+ }
+ comedi_handle_events(dev, s);
+}
+
+static irqreturn_t handle_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned short status;
+ uint32_t plx_status;
+ uint32_t plx_bits;
+
+ plx_status = readl(devpriv->plx9080_iobase + PLX_INTRCS_REG);
+ status = readw(devpriv->main_iobase + HW_STATUS_REG);
+
+ /* an interrupt before all the postconfig stuff gets done could
+ * cause a NULL dereference if we continue through the
+ * interrupt handler */
+ if (!dev->attached)
+ return IRQ_HANDLED;
+
+ handle_ai_interrupt(dev, status, plx_status);
+ handle_ao_interrupt(dev, status, plx_status);
+
+ /* clear possible plx9080 interrupt sources */
+ if (plx_status & ICS_LDIA) { /* clear local doorbell interrupt */
+ plx_bits = readl(devpriv->plx9080_iobase + PLX_DBR_OUT_REG);
+ writel(plx_bits, devpriv->plx9080_iobase + PLX_DBR_OUT_REG);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (devpriv->ai_cmd_running == 0) {
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ return 0;
+ }
+ devpriv->ai_cmd_running = 0;
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ disable_ai_pacing(dev);
+
+ abort_dma(dev, 1);
+
+ return 0;
+}
+
+static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ int range = CR_RANGE(insn->chanspec);
+
+ /* do some initializing */
+ writew(0, devpriv->main_iobase + DAC_CONTROL0_REG);
+
+ /* set range */
+ set_dac_range_bits(dev, &devpriv->dac_control1_bits, chan, range);
+ writew(devpriv->dac_control1_bits,
+ devpriv->main_iobase + DAC_CONTROL1_REG);
+
+ /* write to channel */
+ if (thisboard->layout == LAYOUT_4020) {
+ writew(data[0] & 0xff,
+ devpriv->main_iobase + dac_lsb_4020_reg(chan));
+ writew((data[0] >> 8) & 0xf,
+ devpriv->main_iobase + dac_msb_4020_reg(chan));
+ } else {
+ writew(data[0], devpriv->main_iobase + dac_convert_reg(chan));
+ }
+
+ /* remember output value */
+ s->readback[chan] = data[0];
+
+ return 1;
+}
+
+static void set_dac_control0_reg(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int bits = DAC_ENABLE_BIT | WAVEFORM_GATE_LEVEL_BIT |
+ WAVEFORM_GATE_ENABLE_BIT | WAVEFORM_GATE_SELECT_BIT;
+
+ if (cmd->start_src == TRIG_EXT) {
+ bits |= WAVEFORM_TRIG_EXT_BITS;
+ if (cmd->start_arg & CR_INVERT)
+ bits |= WAVEFORM_TRIG_FALLING_BIT;
+ } else {
+ bits |= WAVEFORM_TRIG_SOFT_BITS;
+ }
+ if (cmd->scan_begin_src == TRIG_EXT) {
+ bits |= DAC_EXT_UPDATE_ENABLE_BIT;
+ if (cmd->scan_begin_arg & CR_INVERT)
+ bits |= DAC_EXT_UPDATE_FALLING_BIT;
+ }
+ writew(bits, devpriv->main_iobase + DAC_CONTROL0_REG);
+}
+
+static void set_dac_control1_reg(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ int channel, range;
+
+ channel = CR_CHAN(cmd->chanlist[i]);
+ range = CR_RANGE(cmd->chanlist[i]);
+ set_dac_range_bits(dev, &devpriv->dac_control1_bits, channel,
+ range);
+ }
+ devpriv->dac_control1_bits |= DAC_SW_GATE_BIT;
+ writew(devpriv->dac_control1_bits,
+ devpriv->main_iobase + DAC_CONTROL1_REG);
+}
+
+static void set_dac_select_reg(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ uint16_t bits;
+ unsigned int first_channel, last_channel;
+
+ first_channel = CR_CHAN(cmd->chanlist[0]);
+ last_channel = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]);
+ if (last_channel < first_channel)
+ dev_err(dev->class_dev,
+ "bug! last ao channel < first ao channel\n");
+
+ bits = (first_channel & 0x7) | (last_channel & 0x7) << 3;
+
+ writew(bits, devpriv->main_iobase + DAC_SELECT_REG);
+}
+
+static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags)
+{
+ return get_divisor(ns, flags) - 2;
+}
+
+static void set_dac_interval_regs(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int divisor;
+
+ if (cmd->scan_begin_src != TRIG_TIMER)
+ return;
+
+ divisor = get_ao_divisor(cmd->scan_begin_arg, cmd->flags);
+ if (divisor > max_counter_value) {
+ dev_err(dev->class_dev, "bug! ao divisor too big\n");
+ divisor = max_counter_value;
+ }
+ writew(divisor & 0xffff,
+ devpriv->main_iobase + DAC_SAMPLE_INTERVAL_LOWER_REG);
+ writew((divisor >> 16) & 0xff,
+ devpriv->main_iobase + DAC_SAMPLE_INTERVAL_UPPER_REG);
+}
+
+static int prep_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ unsigned int nsamples;
+ unsigned int nbytes;
+ int i;
+
+ /* clear queue pointer too, since external queue has
+ * weird interactions with ao fifo */
+ writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
+ writew(0, devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);
+
+ nsamples = cb_pcidas64_ao_fill_buffer(dev, s,
+ devpriv->ao_bounce_buffer,
+ DAC_FIFO_SIZE);
+ if (nsamples == 0)
+ return -1;
+
+ for (i = 0; i < nsamples; i++) {
+ writew(devpriv->ao_bounce_buffer[i],
+ devpriv->main_iobase + DAC_FIFO_REG);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ return 0;
+
+ nbytes = load_ao_dma_buffer(dev, cmd);
+ if (nbytes == 0)
+ return -1;
+ load_ao_dma(dev, cmd);
+
+ dma_start_sync(dev, 0);
+
+ return 0;
+}
+
+static inline int external_ai_queue_in_use(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ if (s->busy)
+ return 0;
+ if (thisboard->layout == LAYOUT_4020)
+ return 0;
+ else if (use_internal_queue_6xxx(cmd))
+ return 0;
+ return 1;
+}
+
+static int ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int retval;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ retval = prep_ao_dma(dev, cmd);
+ if (retval < 0)
+ return -EPIPE;
+
+ set_dac_control0_reg(dev, cmd);
+
+ if (cmd->start_src == TRIG_INT)
+ writew(0, devpriv->main_iobase + DAC_START_REG);
+
+ s->async->inttrig = NULL;
+
+ return 0;
+}
+
+static int ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (external_ai_queue_in_use(dev, s, cmd)) {
+ warn_external_queue(dev);
+ return -EBUSY;
+ }
+ /* disable analog output system during setup */
+ writew(0x0, devpriv->main_iobase + DAC_CONTROL0_REG);
+
+ devpriv->ao_dma_index = 0;
+
+ set_dac_select_reg(dev, cmd);
+ set_dac_interval_regs(dev, cmd);
+ load_first_dma_descriptor(dev, 0, devpriv->ao_dma_desc_bus_addr |
+ PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT);
+
+ set_dac_control1_reg(dev, cmd);
+ s->async->inttrig = ao_inttrig;
+
+ return 0;
+}
+
+static int cb_pcidas64_ao_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i)) {
+ dev_dbg(dev->class_dev,
+ "chanlist must use consecutive channels\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ int err = 0;
+ unsigned int tmp_arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
+ err |= -EINVAL;
+ if (cmd->stop_src != TRIG_COUNT &&
+ cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ thisboard->ao_scan_speed);
+ if (get_ao_divisor(cmd->scan_begin_arg, cmd->flags) >
+ max_counter_value) {
+ cmd->scan_begin_arg = (max_counter_value + 2) *
+ TIMER_BASE;
+ err |= -EINVAL;
+ }
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ tmp_arg = cmd->scan_begin_arg;
+ cmd->scan_begin_arg = get_divisor(cmd->scan_begin_arg,
+ cmd->flags) * TIMER_BASE;
+ if (tmp_arg != cmd->scan_begin_arg)
+ err++;
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas64_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ writew(0x0, devpriv->main_iobase + DAC_CONTROL0_REG);
+ abort_dma(dev, 0);
+ return 0;
+}
+
+static int dio_callback_4020(struct comedi_device *dev,
+ int dir, int port, int data, unsigned long iobase)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ if (dir) {
+ writew(data, devpriv->main_iobase + iobase + 2 * port);
+ return 0;
+ }
+ return readw(devpriv->main_iobase + iobase + 2 * port);
+}
+
+static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int bits;
+
+ bits = readb(dev->mmio + DI_REG);
+ bits &= 0xf;
+ data[1] = bits;
+ data[0] = 0;
+
+ return insn->n;
+}
+
+static int do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ writeb(s->state, dev->mmio + DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int dio_60xx_config_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ writeb(s->io_bits, dev->mmio + DIO_DIRECTION_60XX_REG);
+
+ return insn->n;
+}
+
+static int dio_60xx_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ writeb(s->state, dev->mmio + DIO_DATA_60XX_REG);
+
+ data[1] = readb(dev->mmio + DIO_DATA_60XX_REG);
+
+ return insn->n;
+}
+
+/* pci-6025 8800 caldac:
+ * address 0 == dac channel 0 offset
+ * address 1 == dac channel 0 gain
+ * address 2 == dac channel 1 offset
+ * address 3 == dac channel 1 gain
+ * address 4 == fine adc offset
+ * address 5 == coarse adc offset
+ * address 6 == coarse adc gain
+ * address 7 == fine adc gain
+ */
+/* pci-6402/16 uses all 8 channels for dac:
+ * address 0 == dac channel 0 fine gain
+ * address 1 == dac channel 0 coarse gain
+ * address 2 == dac channel 0 coarse offset
+ * address 3 == dac channel 1 coarse offset
+ * address 4 == dac channel 1 fine gain
+ * address 5 == dac channel 1 coarse gain
+ * address 6 == dac channel 0 fine offset
+ * address 7 == dac channel 1 fine offset
+*/
+
+static int caldac_8800_write(struct comedi_device *dev, unsigned int address,
+ uint8_t value)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ static const int num_caldac_channels = 8;
+ static const int bitstream_length = 11;
+ unsigned int bitstream = ((address & 0x7) << 8) | value;
+ unsigned int bit, register_bits;
+ static const int caldac_8800_udelay = 1;
+
+ if (address >= num_caldac_channels) {
+ dev_err(dev->class_dev, "illegal caldac channel\n");
+ return -1;
+ }
+ for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
+ register_bits = 0;
+ if (bitstream & bit)
+ register_bits |= SERIAL_DATA_IN_BIT;
+ udelay(caldac_8800_udelay);
+ writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
+ register_bits |= SERIAL_CLOCK_BIT;
+ udelay(caldac_8800_udelay);
+ writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
+ }
+ udelay(caldac_8800_udelay);
+ writew(SELECT_8800_BIT, devpriv->main_iobase + CALIBRATION_REG);
+ udelay(caldac_8800_udelay);
+ writew(0, devpriv->main_iobase + CALIBRATION_REG);
+ udelay(caldac_8800_udelay);
+ return 0;
+}
+
+/* 4020 caldacs */
+static int caldac_i2c_write(struct comedi_device *dev,
+ unsigned int caldac_channel, unsigned int value)
+{
+ uint8_t serial_bytes[3];
+ uint8_t i2c_addr;
+ enum pointer_bits {
+ /* manual has gain and offset bits switched */
+ OFFSET_0_2 = 0x1,
+ GAIN_0_2 = 0x2,
+ OFFSET_1_3 = 0x4,
+ GAIN_1_3 = 0x8,
+ };
+ enum data_bits {
+ NOT_CLEAR_REGISTERS = 0x20,
+ };
+
+ switch (caldac_channel) {
+ case 0: /* chan 0 offset */
+ i2c_addr = CALDAC0_I2C_ADDR;
+ serial_bytes[0] = OFFSET_0_2;
+ break;
+ case 1: /* chan 1 offset */
+ i2c_addr = CALDAC0_I2C_ADDR;
+ serial_bytes[0] = OFFSET_1_3;
+ break;
+ case 2: /* chan 2 offset */
+ i2c_addr = CALDAC1_I2C_ADDR;
+ serial_bytes[0] = OFFSET_0_2;
+ break;
+ case 3: /* chan 3 offset */
+ i2c_addr = CALDAC1_I2C_ADDR;
+ serial_bytes[0] = OFFSET_1_3;
+ break;
+ case 4: /* chan 0 gain */
+ i2c_addr = CALDAC0_I2C_ADDR;
+ serial_bytes[0] = GAIN_0_2;
+ break;
+ case 5: /* chan 1 gain */
+ i2c_addr = CALDAC0_I2C_ADDR;
+ serial_bytes[0] = GAIN_1_3;
+ break;
+ case 6: /* chan 2 gain */
+ i2c_addr = CALDAC1_I2C_ADDR;
+ serial_bytes[0] = GAIN_0_2;
+ break;
+ case 7: /* chan 3 gain */
+ i2c_addr = CALDAC1_I2C_ADDR;
+ serial_bytes[0] = GAIN_1_3;
+ break;
+ default:
+ dev_err(dev->class_dev, "invalid caldac channel\n");
+ return -1;
+ }
+ serial_bytes[1] = NOT_CLEAR_REGISTERS | ((value >> 8) & 0xf);
+ serial_bytes[2] = value & 0xff;
+ i2c_write(dev, i2c_addr, serial_bytes, 3);
+ return 0;
+}
+
+static void caldac_write(struct comedi_device *dev, unsigned int channel,
+ unsigned int value)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+
+ switch (thisboard->layout) {
+ case LAYOUT_60XX:
+ case LAYOUT_64XX:
+ caldac_8800_write(dev, channel, value);
+ break;
+ case LAYOUT_4020:
+ caldac_i2c_write(dev, channel, value);
+ break;
+ default:
+ break;
+ }
+}
+
+static int cb_pcidas64_calib_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * Programming the calib device is slow. Only write the
+ * last data value if the value has changed.
+ */
+ if (insn->n) {
+ unsigned int val = data[insn->n - 1];
+
+ if (s->readback[chan] != val) {
+ caldac_write(dev, chan, val);
+ s->readback[chan] = val;
+ }
+ }
+
+ return insn->n;
+}
+
+static void ad8402_write(struct comedi_device *dev, unsigned int channel,
+ unsigned int value)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ static const int bitstream_length = 10;
+ unsigned int bit, register_bits;
+ unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff);
+ static const int ad8402_udelay = 1;
+
+ register_bits = SELECT_8402_64XX_BIT;
+ udelay(ad8402_udelay);
+ writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
+
+ for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
+ if (bitstream & bit)
+ register_bits |= SERIAL_DATA_IN_BIT;
+ else
+ register_bits &= ~SERIAL_DATA_IN_BIT;
+ udelay(ad8402_udelay);
+ writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
+ udelay(ad8402_udelay);
+ writew(register_bits | SERIAL_CLOCK_BIT,
+ devpriv->main_iobase + CALIBRATION_REG);
+ }
+
+ udelay(ad8402_udelay);
+ writew(0, devpriv->main_iobase + CALIBRATION_REG);
+}
+
+/* for pci-das6402/16, channel 0 is analog input gain and channel 1 is offset */
+static int cb_pcidas64_ad8402_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * Programming the calib device is slow. Only write the
+ * last data value if the value has changed.
+ */
+ if (insn->n) {
+ unsigned int val = data[insn->n - 1];
+
+ if (s->readback[chan] != val) {
+ ad8402_write(dev, chan, val);
+ s->readback[chan] = val;
+ }
+ }
+
+ return insn->n;
+}
+
+static uint16_t read_eeprom(struct comedi_device *dev, uint8_t address)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ static const int bitstream_length = 11;
+ static const int read_command = 0x6;
+ unsigned int bitstream = (read_command << 8) | address;
+ unsigned int bit;
+ void __iomem * const plx_control_addr =
+ devpriv->plx9080_iobase + PLX_CONTROL_REG;
+ uint16_t value;
+ static const int value_length = 16;
+ static const int eeprom_udelay = 1;
+
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits &= ~CTL_EE_CLK & ~CTL_EE_CS;
+ /* make sure we don't send anything to the i2c bus on 4020 */
+ devpriv->plx_control_bits |= CTL_USERO;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ /* activate serial eeprom */
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits |= CTL_EE_CS;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+
+ /* write read command and desired memory address */
+ for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
+ /* set bit to be written */
+ udelay(eeprom_udelay);
+ if (bitstream & bit)
+ devpriv->plx_control_bits |= CTL_EE_W;
+ else
+ devpriv->plx_control_bits &= ~CTL_EE_W;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ /* clock in bit */
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits |= CTL_EE_CLK;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits &= ~CTL_EE_CLK;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ }
+ /* read back value from eeprom memory location */
+ value = 0;
+ for (bit = 1 << (value_length - 1); bit; bit >>= 1) {
+ /* clock out bit */
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits |= CTL_EE_CLK;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits &= ~CTL_EE_CLK;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(eeprom_udelay);
+ if (readl(plx_control_addr) & CTL_EE_R)
+ value |= bit;
+ }
+
+ /* deactivate eeprom serial input */
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits &= ~CTL_EE_CS;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+
+ return value;
+}
+
+static int eeprom_read_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[0] = read_eeprom(dev, CR_CHAN(insn->chanspec));
+
+ return 1;
+}
+
+/* Allocate and initialize the subdevice structures.
+ */
+static int setup_subdevices(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = dev->board_ptr;
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int i;
+ int ret;
+
+ ret = comedi_alloc_subdevices(dev, 10);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DITHER | SDF_CMD_READ;
+ if (thisboard->layout == LAYOUT_60XX)
+ s->subdev_flags |= SDF_COMMON | SDF_DIFF;
+ else if (thisboard->layout == LAYOUT_64XX)
+ s->subdev_flags |= SDF_DIFF;
+ /* XXX Number of inputs in differential mode is ignored */
+ s->n_chan = thisboard->ai_se_chans;
+ s->len_chanlist = 0x2000;
+ s->maxdata = (1 << thisboard->ai_bits) - 1;
+ s->range_table = thisboard->ai_range_table;
+ s->insn_read = ai_rinsn;
+ s->insn_config = ai_config_insn;
+ s->do_cmd = ai_cmd;
+ s->do_cmdtest = ai_cmdtest;
+ s->cancel = ai_cancel;
+ if (thisboard->layout == LAYOUT_4020) {
+ uint8_t data;
+ /* set adc to read from inputs
+ * (not internal calibration sources) */
+ devpriv->i2c_cal_range_bits = adc_src_4020_bits(4);
+ /* set channels to +-5 volt input ranges */
+ for (i = 0; i < s->n_chan; i++)
+ devpriv->i2c_cal_range_bits |= attenuate_bit(i);
+ data = devpriv->i2c_cal_range_bits;
+ i2c_write(dev, RANGE_CAL_I2C_ADDR, &data, sizeof(data));
+ }
+
+ /* analog output subdevice */
+ s = &dev->subdevices[1];
+ if (thisboard->ao_nchan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE |
+ SDF_GROUND | SDF_CMD_WRITE;
+ s->n_chan = thisboard->ao_nchan;
+ s->maxdata = (1 << thisboard->ao_bits) - 1;
+ s->range_table = thisboard->ao_range_table;
+ s->insn_write = ao_winsn;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ if (ao_cmd_is_supported(thisboard)) {
+ dev->write_subdev = s;
+ s->do_cmdtest = ao_cmdtest;
+ s->do_cmd = ao_cmd;
+ s->len_chanlist = thisboard->ao_nchan;
+ s->cancel = ao_cancel;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* digital input */
+ s = &dev->subdevices[2];
+ if (thisboard->layout == LAYOUT_64XX) {
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = di_rbits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* digital output */
+ if (thisboard->layout == LAYOUT_64XX) {
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = do_wbits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* 8255 */
+ s = &dev->subdevices[4];
+ if (thisboard->has_8255) {
+ if (thisboard->layout == LAYOUT_4020) {
+ ret = subdev_8255_init(dev, s, dio_callback_4020,
+ I8255_4020_REG);
+ } else {
+ ret = subdev_8255_mm_init(dev, s, NULL,
+ DIO_8255_OFFSET);
+ }
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* 8 channel dio for 60xx */
+ s = &dev->subdevices[5];
+ if (thisboard->layout == LAYOUT_60XX) {
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = dio_60xx_config_insn;
+ s->insn_bits = dio_60xx_wbits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* caldac */
+ s = &dev->subdevices[6];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = 8;
+ if (thisboard->layout == LAYOUT_4020)
+ s->maxdata = 0xfff;
+ else
+ s->maxdata = 0xff;
+ s->insn_write = cb_pcidas64_calib_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++) {
+ caldac_write(dev, i, s->maxdata / 2);
+ s->readback[i] = s->maxdata / 2;
+ }
+
+ /* 2 channel ad8402 potentiometer */
+ s = &dev->subdevices[7];
+ if (thisboard->layout == LAYOUT_64XX) {
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = 2;
+ s->maxdata = 0xff;
+ s->insn_write = cb_pcidas64_ad8402_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++) {
+ ad8402_write(dev, i, s->maxdata / 2);
+ s->readback[i] = s->maxdata / 2;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* serial EEPROM, if present */
+ s = &dev->subdevices[8];
+ if (readl(devpriv->plx9080_iobase + PLX_CONTROL_REG) & CTL_EECHK) {
+ s->type = COMEDI_SUBD_MEMORY;
+ s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
+ s->n_chan = 128;
+ s->maxdata = 0xffff;
+ s->insn_read = eeprom_read_insn;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* user counter subd XXX */
+ s = &dev->subdevices[9];
+ s->type = COMEDI_SUBD_UNUSED;
+
+ return 0;
+}
+
+static int auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct pcidas64_board *thisboard = NULL;
+ struct pcidas64_private *devpriv;
+ uint32_t local_range, local_decode;
+ int retval;
+
+ if (context < ARRAY_SIZE(pcidas64_boards))
+ thisboard = &pcidas64_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ retval = comedi_pci_enable(dev);
+ if (retval)
+ return retval;
+ pci_set_master(pcidev);
+
+ /* Initialize dev->board_name */
+ dev->board_name = thisboard->name;
+
+ devpriv->main_phys_iobase = pci_resource_start(pcidev, 2);
+ devpriv->dio_counter_phys_iobase = pci_resource_start(pcidev, 3);
+
+ devpriv->plx9080_iobase = pci_ioremap_bar(pcidev, 0);
+ devpriv->main_iobase = pci_ioremap_bar(pcidev, 2);
+ dev->mmio = pci_ioremap_bar(pcidev, 3);
+
+ if (!devpriv->plx9080_iobase || !devpriv->main_iobase || !dev->mmio) {
+ dev_warn(dev->class_dev, "failed to remap io memory\n");
+ return -ENOMEM;
+ }
+
+ /* figure out what local addresses are */
+ local_range = readl(devpriv->plx9080_iobase + PLX_LAS0RNG_REG) &
+ LRNG_MEM_MASK;
+ local_decode = readl(devpriv->plx9080_iobase + PLX_LAS0MAP_REG) &
+ local_range & LMAP_MEM_MASK;
+ devpriv->local0_iobase = ((uint32_t)devpriv->main_phys_iobase &
+ ~local_range) | local_decode;
+ local_range = readl(devpriv->plx9080_iobase + PLX_LAS1RNG_REG) &
+ LRNG_MEM_MASK;
+ local_decode = readl(devpriv->plx9080_iobase + PLX_LAS1MAP_REG) &
+ local_range & LMAP_MEM_MASK;
+ devpriv->local1_iobase = ((uint32_t)devpriv->dio_counter_phys_iobase &
+ ~local_range) | local_decode;
+
+ retval = alloc_and_init_dma_members(dev);
+ if (retval < 0)
+ return retval;
+
+ devpriv->hw_revision =
+ hw_revision(dev, readw(devpriv->main_iobase + HW_STATUS_REG));
+ dev_dbg(dev->class_dev, "stc hardware revision %i\n",
+ devpriv->hw_revision);
+ init_plx9080(dev);
+ init_stc_registers(dev);
+
+ retval = request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (retval) {
+ dev_dbg(dev->class_dev, "unable to allocate irq %u\n",
+ pcidev->irq);
+ return retval;
+ }
+ dev->irq = pcidev->irq;
+ dev_dbg(dev->class_dev, "irq %u\n", dev->irq);
+
+ retval = setup_subdevices(dev);
+ if (retval < 0)
+ return retval;
+
+ return 0;
+}
+
+static void detach(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->plx9080_iobase) {
+ disable_plx_interrupts(dev);
+ iounmap(devpriv->plx9080_iobase);
+ }
+ if (devpriv->main_iobase)
+ iounmap(devpriv->main_iobase);
+ if (dev->mmio)
+ iounmap(dev->mmio);
+ }
+ comedi_pci_disable(dev);
+ cb_pcidas64_free_dma(dev);
+}
+
+static struct comedi_driver cb_pcidas64_driver = {
+ .driver_name = "cb_pcidas64",
+ .module = THIS_MODULE,
+ .auto_attach = auto_attach,
+ .detach = detach,
+};
+
+static int cb_pcidas64_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcidas64_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcidas64_pci_table[] = {
+ { PCI_VDEVICE(CB, 0x001d), BOARD_PCIDAS6402_16 },
+ { PCI_VDEVICE(CB, 0x001e), BOARD_PCIDAS6402_12 },
+ { PCI_VDEVICE(CB, 0x0035), BOARD_PCIDAS64_M1_16 },
+ { PCI_VDEVICE(CB, 0x0036), BOARD_PCIDAS64_M2_16 },
+ { PCI_VDEVICE(CB, 0x0037), BOARD_PCIDAS64_M3_16 },
+ { PCI_VDEVICE(CB, 0x0052), BOARD_PCIDAS4020_12 },
+ { PCI_VDEVICE(CB, 0x005d), BOARD_PCIDAS6023 },
+ { PCI_VDEVICE(CB, 0x005e), BOARD_PCIDAS6025 },
+ { PCI_VDEVICE(CB, 0x005f), BOARD_PCIDAS6030 },
+ { PCI_VDEVICE(CB, 0x0060), BOARD_PCIDAS6031 },
+ { PCI_VDEVICE(CB, 0x0061), BOARD_PCIDAS6032 },
+ { PCI_VDEVICE(CB, 0x0062), BOARD_PCIDAS6033 },
+ { PCI_VDEVICE(CB, 0x0063), BOARD_PCIDAS6034 },
+ { PCI_VDEVICE(CB, 0x0064), BOARD_PCIDAS6035 },
+ { PCI_VDEVICE(CB, 0x0065), BOARD_PCIDAS6040 },
+ { PCI_VDEVICE(CB, 0x0066), BOARD_PCIDAS6052 },
+ { PCI_VDEVICE(CB, 0x0067), BOARD_PCIDAS6070 },
+ { PCI_VDEVICE(CB, 0x0068), BOARD_PCIDAS6071 },
+ { PCI_VDEVICE(CB, 0x006f), BOARD_PCIDAS6036 },
+ { PCI_VDEVICE(CB, 0x0078), BOARD_PCIDAS6013 },
+ { PCI_VDEVICE(CB, 0x0079), BOARD_PCIDAS6014 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcidas64_pci_table);
+
+static struct pci_driver cb_pcidas64_pci_driver = {
+ .name = "cb_pcidas64",
+ .id_table = cb_pcidas64_pci_table,
+ .probe = cb_pcidas64_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcidas64_driver, cb_pcidas64_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/cb_pcidda.c b/drivers/staging/comedi/drivers/cb_pcidda.c
new file mode 100644
index 000000000..30c9e27d1
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcidda.c
@@ -0,0 +1,428 @@
+/*
+ * comedi/drivers/cb_pcidda.c
+ * Driver for the ComputerBoards / MeasurementComputing PCI-DDA series.
+ *
+ * Copyright (C) 2001 Ivan Martinez <ivanmr@altavista.com>
+ * Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: cb_pcidda
+ * Description: MeasurementComputing PCI-DDA series
+ * Devices: [Measurement Computing] PCI-DDA08/12 (pci-dda08/12),
+ * PCI-DDA04/12 (pci-dda04/12), PCI-DDA02/12 (pci-dda02/12),
+ * PCI-DDA08/16 (pci-dda08/16), PCI-DDA04/16 (pci-dda04/16),
+ * PCI-DDA02/16 (pci-dda02/16)
+ * Author: Ivan Martinez <ivanmr@altavista.com>
+ * Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ *
+ * Configuration options: not applicable, uses PCI auto config
+ *
+ * Only simple analog output writing is supported.
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+#include "8255.h"
+
+#define EEPROM_SIZE 128 /* number of entries in eeprom */
+/* maximum number of ao channels for supported boards */
+#define MAX_AO_CHANNELS 8
+
+/* Digital I/O registers */
+#define CB_DDA_DIO0_8255_BASE 0x00
+#define CB_DDA_DIO1_8255_BASE 0x04
+
+/* DAC registers */
+#define CB_DDA_DA_CTRL_REG 0x00 /* D/A Control Register */
+#define CB_DDA_DA_CTRL_SU (1 << 0) /* Simultaneous update */
+#define CB_DDA_DA_CTRL_EN (1 << 1) /* Enable specified DAC */
+#define CB_DDA_DA_CTRL_DAC(x) ((x) << 2) /* Specify DAC channel */
+#define CB_DDA_DA_CTRL_RANGE2V5 (0 << 6) /* 2.5V range */
+#define CB_DDA_DA_CTRL_RANGE5V (2 << 6) /* 5V range */
+#define CB_DDA_DA_CTRL_RANGE10V (3 << 6) /* 10V range */
+#define CB_DDA_DA_CTRL_UNIP (1 << 8) /* Unipolar range */
+
+#define DACALIBRATION1 4 /* D/A CALIBRATION REGISTER 1 */
+/* write bits */
+/* serial data input for eeprom, caldacs, reference dac */
+#define SERIAL_IN_BIT 0x1
+#define CAL_CHANNEL_MASK (0x7 << 1)
+#define CAL_CHANNEL_BITS(channel) (((channel) << 1) & CAL_CHANNEL_MASK)
+/* read bits */
+#define CAL_COUNTER_MASK 0x1f
+/* calibration counter overflow status bit */
+#define CAL_COUNTER_OVERFLOW_BIT 0x20
+/* analog output is less than reference dac voltage */
+#define AO_BELOW_REF_BIT 0x40
+#define SERIAL_OUT_BIT 0x80 /* serial data out, for reading from eeprom */
+
+#define DACALIBRATION2 6 /* D/A CALIBRATION REGISTER 2 */
+#define SELECT_EEPROM_BIT 0x1 /* send serial data in to eeprom */
+/* don't send serial data to MAX542 reference dac */
+#define DESELECT_REF_DAC_BIT 0x2
+/* don't send serial data to caldac n */
+#define DESELECT_CALDAC_BIT(n) (0x4 << (n))
+/* manual says to set this bit with no explanation */
+#define DUMMY_BIT 0x40
+
+#define CB_DDA_DA_DATA_REG(x) (0x08 + ((x) * 2))
+
+/* Offsets for the caldac channels */
+#define CB_DDA_CALDAC_FINE_GAIN 0
+#define CB_DDA_CALDAC_COURSE_GAIN 1
+#define CB_DDA_CALDAC_COURSE_OFFSET 2
+#define CB_DDA_CALDAC_FINE_OFFSET 3
+
+static const struct comedi_lrange cb_pcidda_ranges = {
+ 6, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5)
+ }
+};
+
+enum cb_pcidda_boardid {
+ BOARD_DDA02_12,
+ BOARD_DDA04_12,
+ BOARD_DDA08_12,
+ BOARD_DDA02_16,
+ BOARD_DDA04_16,
+ BOARD_DDA08_16,
+};
+
+struct cb_pcidda_board {
+ const char *name;
+ int ao_chans;
+ int ao_bits;
+};
+
+static const struct cb_pcidda_board cb_pcidda_boards[] = {
+ [BOARD_DDA02_12] = {
+ .name = "pci-dda02/12",
+ .ao_chans = 2,
+ .ao_bits = 12,
+ },
+ [BOARD_DDA04_12] = {
+ .name = "pci-dda04/12",
+ .ao_chans = 4,
+ .ao_bits = 12,
+ },
+ [BOARD_DDA08_12] = {
+ .name = "pci-dda08/12",
+ .ao_chans = 8,
+ .ao_bits = 12,
+ },
+ [BOARD_DDA02_16] = {
+ .name = "pci-dda02/16",
+ .ao_chans = 2,
+ .ao_bits = 16,
+ },
+ [BOARD_DDA04_16] = {
+ .name = "pci-dda04/16",
+ .ao_chans = 4,
+ .ao_bits = 16,
+ },
+ [BOARD_DDA08_16] = {
+ .name = "pci-dda08/16",
+ .ao_chans = 8,
+ .ao_bits = 16,
+ },
+};
+
+struct cb_pcidda_private {
+ unsigned long daqio;
+ /* bits last written to da calibration register 1 */
+ unsigned int dac_cal1_bits;
+ /* current range settings for output channels */
+ unsigned int ao_range[MAX_AO_CHANNELS];
+ u16 eeprom_data[EEPROM_SIZE]; /* software copy of board's eeprom */
+};
+
+/* lowlevel read from eeprom */
+static unsigned int cb_pcidda_serial_in(struct comedi_device *dev)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ unsigned int value = 0;
+ int i;
+ const int value_width = 16; /* number of bits wide values are */
+
+ for (i = 1; i <= value_width; i++) {
+ /* read bits most significant bit first */
+ if (inw_p(devpriv->daqio + DACALIBRATION1) & SERIAL_OUT_BIT)
+ value |= 1 << (value_width - i);
+ }
+
+ return value;
+}
+
+/* lowlevel write to eeprom/dac */
+static void cb_pcidda_serial_out(struct comedi_device *dev, unsigned int value,
+ unsigned int num_bits)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ int i;
+
+ for (i = 1; i <= num_bits; i++) {
+ /* send bits most significant bit first */
+ if (value & (1 << (num_bits - i)))
+ devpriv->dac_cal1_bits |= SERIAL_IN_BIT;
+ else
+ devpriv->dac_cal1_bits &= ~SERIAL_IN_BIT;
+ outw_p(devpriv->dac_cal1_bits, devpriv->daqio + DACALIBRATION1);
+ }
+}
+
+/* reads a 16 bit value from board's eeprom */
+static unsigned int cb_pcidda_read_eeprom(struct comedi_device *dev,
+ unsigned int address)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ unsigned int i;
+ unsigned int cal2_bits;
+ unsigned int value;
+ /* one caldac for every two dac channels */
+ const int max_num_caldacs = 4;
+ /* bits to send to tell eeprom we want to read */
+ const int read_instruction = 0x6;
+ const int instruction_length = 3;
+ const int address_length = 8;
+
+ /* send serial output stream to eeprom */
+ cal2_bits = SELECT_EEPROM_BIT | DESELECT_REF_DAC_BIT | DUMMY_BIT;
+ /* deactivate caldacs (one caldac for every two channels) */
+ for (i = 0; i < max_num_caldacs; i++)
+ cal2_bits |= DESELECT_CALDAC_BIT(i);
+ outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2);
+
+ /* tell eeprom we want to read */
+ cb_pcidda_serial_out(dev, read_instruction, instruction_length);
+ /* send address we want to read from */
+ cb_pcidda_serial_out(dev, address, address_length);
+
+ value = cb_pcidda_serial_in(dev);
+
+ /* deactivate eeprom */
+ cal2_bits &= ~SELECT_EEPROM_BIT;
+ outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2);
+
+ return value;
+}
+
+/* writes to 8 bit calibration dacs */
+static void cb_pcidda_write_caldac(struct comedi_device *dev,
+ unsigned int caldac, unsigned int channel,
+ unsigned int value)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ unsigned int cal2_bits;
+ unsigned int i;
+ /* caldacs use 3 bit channel specification */
+ const int num_channel_bits = 3;
+ const int num_caldac_bits = 8; /* 8 bit calibration dacs */
+ /* one caldac for every two dac channels */
+ const int max_num_caldacs = 4;
+
+ /* write 3 bit channel */
+ cb_pcidda_serial_out(dev, channel, num_channel_bits);
+ /* write 8 bit caldac value */
+ cb_pcidda_serial_out(dev, value, num_caldac_bits);
+
+/*
+* latch stream into appropriate caldac deselect reference dac
+*/
+ cal2_bits = DESELECT_REF_DAC_BIT | DUMMY_BIT;
+ /* deactivate caldacs (one caldac for every two channels) */
+ for (i = 0; i < max_num_caldacs; i++)
+ cal2_bits |= DESELECT_CALDAC_BIT(i);
+ /* activate the caldac we want */
+ cal2_bits &= ~DESELECT_CALDAC_BIT(caldac);
+ outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2);
+ /* deactivate caldac */
+ cal2_bits |= DESELECT_CALDAC_BIT(caldac);
+ outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2);
+}
+
+/* set caldacs to eeprom values for given channel and range */
+static void cb_pcidda_calibrate(struct comedi_device *dev, unsigned int channel,
+ unsigned int range)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ unsigned int caldac = channel / 2; /* two caldacs per channel */
+ unsigned int chan = 4 * (channel % 2); /* caldac channel base */
+ unsigned int index = 2 * range + 12 * channel;
+ unsigned int offset;
+ unsigned int gain;
+
+ /* save range so we can tell when we need to readjust calibration */
+ devpriv->ao_range[channel] = range;
+
+ /* get values from eeprom data */
+ offset = devpriv->eeprom_data[0x7 + index];
+ gain = devpriv->eeprom_data[0x8 + index];
+
+ /* set caldacs */
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_COURSE_OFFSET,
+ (offset >> 8) & 0xff);
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_FINE_OFFSET,
+ offset & 0xff);
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_COURSE_GAIN,
+ (gain >> 8) & 0xff);
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_FINE_GAIN,
+ gain & 0xff);
+}
+
+static int cb_pcidda_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int ctrl;
+
+ if (range != devpriv->ao_range[channel])
+ cb_pcidda_calibrate(dev, channel, range);
+
+ ctrl = CB_DDA_DA_CTRL_EN | CB_DDA_DA_CTRL_DAC(channel);
+
+ switch (range) {
+ case 0:
+ case 3:
+ ctrl |= CB_DDA_DA_CTRL_RANGE10V;
+ break;
+ case 1:
+ case 4:
+ ctrl |= CB_DDA_DA_CTRL_RANGE5V;
+ break;
+ case 2:
+ case 5:
+ ctrl |= CB_DDA_DA_CTRL_RANGE2V5;
+ break;
+ }
+
+ if (range > 2)
+ ctrl |= CB_DDA_DA_CTRL_UNIP;
+
+ outw(ctrl, devpriv->daqio + CB_DDA_DA_CTRL_REG);
+
+ outw(data[0], devpriv->daqio + CB_DDA_DA_DATA_REG(channel));
+
+ return insn->n;
+}
+
+static int cb_pcidda_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct cb_pcidda_board *thisboard = NULL;
+ struct cb_pcidda_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ int ret;
+
+ if (context < ARRAY_SIZE(cb_pcidda_boards))
+ thisboard = &cb_pcidda_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+ devpriv->daqio = pci_resource_start(pcidev, 3);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = thisboard->ao_chans;
+ s->maxdata = (1 << thisboard->ao_bits) - 1;
+ s->range_table = &cb_pcidda_ranges;
+ s->insn_write = cb_pcidda_ao_insn_write;
+
+ /* two 8255 digital io subdevices */
+ for (i = 0; i < 2; i++) {
+ s = &dev->subdevices[1 + i];
+ ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE);
+ if (ret)
+ return ret;
+ }
+
+ /* Read the caldac eeprom data */
+ for (i = 0; i < EEPROM_SIZE; i++)
+ devpriv->eeprom_data[i] = cb_pcidda_read_eeprom(dev, i);
+
+ /* set calibrations dacs */
+ for (i = 0; i < thisboard->ao_chans; i++)
+ cb_pcidda_calibrate(dev, i, devpriv->ao_range[i]);
+
+ return 0;
+}
+
+static struct comedi_driver cb_pcidda_driver = {
+ .driver_name = "cb_pcidda",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcidda_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int cb_pcidda_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcidda_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcidda_pci_table[] = {
+ { PCI_VDEVICE(CB, 0x0020), BOARD_DDA02_12 },
+ { PCI_VDEVICE(CB, 0x0021), BOARD_DDA04_12 },
+ { PCI_VDEVICE(CB, 0x0022), BOARD_DDA08_12 },
+ { PCI_VDEVICE(CB, 0x0023), BOARD_DDA02_16 },
+ { PCI_VDEVICE(CB, 0x0024), BOARD_DDA04_16 },
+ { PCI_VDEVICE(CB, 0x0025), BOARD_DDA08_16 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcidda_pci_table);
+
+static struct pci_driver cb_pcidda_pci_driver = {
+ .name = "cb_pcidda",
+ .id_table = cb_pcidda_pci_table,
+ .probe = cb_pcidda_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcidda_driver, cb_pcidda_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/cb_pcimdas.c b/drivers/staging/comedi/drivers/cb_pcimdas.c
new file mode 100644
index 000000000..4ebf5aae5
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcimdas.c
@@ -0,0 +1,482 @@
+/*
+ * comedi/drivers/cb_pcimdas.c
+ * Comedi driver for Computer Boards PCIM-DAS1602/16 and PCIe-DAS1602/16
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: cb_pcimdas
+ * Description: Measurement Computing PCI Migration series boards
+ * Devices: [ComputerBoards] PCIM-DAS1602/16 (cb_pcimdas), PCIe-DAS1602/16
+ * Author: Richard Bytheway
+ * Updated: Mon, 13 Oct 2014 11:57:39 +0000
+ * Status: experimental
+ *
+ * Written to support the PCIM-DAS1602/16 and PCIe-DAS1602/16.
+ *
+ * Configuration Options:
+ * none
+ *
+ * Manual configuration of PCI(e) cards is not supported; they are configured
+ * automatically.
+ *
+ * Developed from cb_pcidas and skel by Richard Bytheway (mocelet@sucs.org).
+ * Only supports DIO, AO and simple AI in it's present form.
+ * No interrupts, multi channel or FIFO AI,
+ * although the card looks like it could support this.
+ *
+ * http://www.mccdaq.com/PDFs/Manuals/pcim-das1602-16.pdf
+ * http://www.mccdaq.com/PDFs/Manuals/pcie-das1602-16.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "plx9052.h"
+#include "8255.h"
+
+/*
+ * PCI Bar 1 Register map
+ * see plx9052.h for register and bit defines
+ */
+
+/*
+ * PCI Bar 2 Register map (devpriv->daqio)
+ */
+#define PCIMDAS_AI_REG 0x00
+#define PCIMDAS_AI_SOFTTRIG_REG 0x00
+#define PCIMDAS_AO_REG(x) (0x02 + ((x) * 2))
+
+/*
+ * PCI Bar 3 Register map (devpriv->BADR3)
+ */
+#define PCIMDAS_MUX_REG 0x00
+#define PCIMDAS_MUX(_lo, _hi) ((_lo) | ((_hi) << 4))
+#define PCIMDAS_DI_DO_REG 0x01
+#define PCIMDAS_STATUS_REG 0x02
+#define PCIMDAS_STATUS_EOC BIT(7)
+#define PCIMDAS_STATUS_UB BIT(6)
+#define PCIMDAS_STATUS_MUX BIT(5)
+#define PCIMDAS_STATUS_CLK BIT(4)
+#define PCIMDAS_STATUS_TO_CURR_MUX(x) ((x) & 0xf)
+#define PCIMDAS_CONV_STATUS_REG 0x03
+#define PCIMDAS_CONV_STATUS_EOC BIT(7)
+#define PCIMDAS_CONV_STATUS_EOB BIT(6)
+#define PCIMDAS_CONV_STATUS_EOA BIT(5)
+#define PCIMDAS_CONV_STATUS_FNE BIT(4)
+#define PCIMDAS_CONV_STATUS_FHF BIT(3)
+#define PCIMDAS_CONV_STATUS_OVERRUN BIT(2)
+#define PCIMDAS_IRQ_REG 0x04
+#define PCIMDAS_IRQ_INTE BIT(7)
+#define PCIMDAS_IRQ_INT BIT(6)
+#define PCIMDAS_IRQ_OVERRUN BIT(4)
+#define PCIMDAS_IRQ_EOA BIT(3)
+#define PCIMDAS_IRQ_EOA_INT_SEL BIT(2)
+#define PCIMDAS_IRQ_INTSEL(x) ((x) << 0)
+#define PCIMDAS_IRQ_INTSEL_EOC PCIMDAS_IRQ_INTSEL(0)
+#define PCIMDAS_IRQ_INTSEL_FNE PCIMDAS_IRQ_INTSEL(1)
+#define PCIMDAS_IRQ_INTSEL_EOB PCIMDAS_IRQ_INTSEL(2)
+#define PCIMDAS_IRQ_INTSEL_FHF_EOA PCIMDAS_IRQ_INTSEL(3)
+#define PCIMDAS_PACER_REG 0x05
+#define PCIMDAS_PACER_GATE_STATUS BIT(6)
+#define PCIMDAS_PACER_GATE_POL BIT(5)
+#define PCIMDAS_PACER_GATE_LATCH BIT(4)
+#define PCIMDAS_PACER_GATE_EN BIT(3)
+#define PCIMDAS_PACER_EXT_PACER_POL BIT(2)
+#define PCIMDAS_PACER_SRC(x) ((x) << 0)
+#define PCIMDAS_PACER_SRC_POLLED PCIMDAS_PACER_SRC(0)
+#define PCIMDAS_PACER_SRC_EXT PCIMDAS_PACER_SRC(2)
+#define PCIMDAS_PACER_SRC_INT PCIMDAS_PACER_SRC(3)
+#define PCIMDAS_PACER_SRC_MASK (3 << 0)
+#define PCIMDAS_BURST_REG 0x06
+#define PCIMDAS_BURST_BME BIT(1)
+#define PCIMDAS_BURST_CONV_EN BIT(0)
+#define PCIMDAS_GAIN_REG 0x07
+#define PCIMDAS_8254_BASE 0x08
+#define PCIMDAS_USER_CNTR_REG 0x0c
+#define PCIMDAS_USER_CNTR_CTR1_CLK_SEL BIT(0)
+#define PCIMDAS_RESIDUE_MSB_REG 0x0d
+#define PCIMDAS_RESIDUE_LSB_REG 0x0e
+
+/*
+ * PCI Bar 4 Register map (dev->iobase)
+ */
+#define PCIMDAS_8255_BASE 0x00
+
+static const struct comedi_lrange cb_pcimdas_ai_bip_range = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange cb_pcimdas_ai_uni_range = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+/*
+ * The Analog Output range is not programmable. The DAC ranges are
+ * jumper-settable on the board. The settings are not software-readable.
+ */
+static const struct comedi_lrange cb_pcimdas_ao_range = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ UNI_RANGE(10),
+ UNI_RANGE(5)
+ }
+};
+
+/*
+ * this structure is for data unique to this hardware driver. If
+ * several hardware drivers keep similar information in this structure,
+ * feel free to suggest moving the variable to the struct comedi_device
+ * struct.
+ */
+struct cb_pcimdas_private {
+ /* base addresses */
+ unsigned long daqio;
+ unsigned long BADR3;
+};
+
+static int cb_pcimdas_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG);
+ if ((status & PCIMDAS_STATUS_EOC) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int cb_pcimdas_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int n;
+ unsigned int d;
+ int ret;
+
+ /* only support sw initiated reads from a single channel */
+
+ /* configure for sw initiated read */
+ d = inb(devpriv->BADR3 + PCIMDAS_PACER_REG);
+ if ((d & PCIMDAS_PACER_SRC_MASK) != PCIMDAS_PACER_SRC_POLLED) {
+ d &= ~PCIMDAS_PACER_SRC_MASK;
+ d |= PCIMDAS_PACER_SRC_POLLED;
+ outb(d, devpriv->BADR3 + PCIMDAS_PACER_REG);
+ }
+
+ /* set bursting off, conversions on */
+ outb(PCIMDAS_BURST_CONV_EN, devpriv->BADR3 + PCIMDAS_BURST_REG);
+
+ /* set range */
+ outb(range, devpriv->BADR3 + PCIMDAS_GAIN_REG);
+
+ /* set mux for single channel scan */
+ outb(PCIMDAS_MUX(chan, chan), devpriv->BADR3 + PCIMDAS_MUX_REG);
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion */
+ outw(0, devpriv->daqio + PCIMDAS_AI_SOFTTRIG_REG);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, cb_pcimdas_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ data[n] = inw(devpriv->daqio + PCIMDAS_AI_REG);
+ }
+
+ /* return the number of samples read/written */
+ return n;
+}
+
+static int cb_pcimdas_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, devpriv->daqio + PCIMDAS_AO_REG(chan));
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int cb_pcimdas_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int val;
+
+ val = inb(devpriv->BADR3 + PCIMDAS_DI_DO_REG);
+
+ data[1] = val & 0x0f;
+
+ return insn->n;
+}
+
+static int cb_pcimdas_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, devpriv->BADR3 + PCIMDAS_DI_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int cb_pcimdas_counter_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int ctrl;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ switch (data[1]) {
+ case 0: /* internal 100 kHz clock */
+ ctrl = PCIMDAS_USER_CNTR_CTR1_CLK_SEL;
+ break;
+ case 1: /* external clk on pin 21 */
+ ctrl = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ outb(ctrl, devpriv->BADR3 + PCIMDAS_USER_CNTR_REG);
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ ctrl = inb(devpriv->BADR3 + PCIMDAS_USER_CNTR_REG);
+ if (ctrl & PCIMDAS_USER_CNTR_CTR1_CLK_SEL) {
+ data[1] = 0;
+ data[2] = I8254_OSC_BASE_100KHZ;
+ } else {
+ data[1] = 1;
+ data[2] = 0;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static unsigned int cb_pcimdas_pacer_clk(struct comedi_device *dev)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int status;
+
+ /* The Pacer Clock jumper selects a 10 MHz or 1 MHz clock */
+ status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG);
+ if (status & PCIMDAS_STATUS_CLK)
+ return I8254_OSC_BASE_10MHZ;
+ return I8254_OSC_BASE_1MHZ;
+}
+
+static bool cb_pcimdas_is_ai_se(struct comedi_device *dev)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int status;
+
+ /*
+ * The number of Analog Input channels is set with the
+ * Analog Input Mode Switch on the board. The board can
+ * have 16 single-ended or 8 differential channels.
+ */
+ status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG);
+ return status & PCIMDAS_STATUS_MUX;
+}
+
+static bool cb_pcimdas_is_ai_uni(struct comedi_device *dev)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int status;
+
+ /*
+ * The Analog Input range polarity is set with the
+ * Analog Input Polarity Switch on the board. The
+ * inputs can be set to Unipolar or Bipolar ranges.
+ */
+ status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG);
+ return status & PCIMDAS_STATUS_UB;
+}
+
+static int cb_pcimdas_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct cb_pcimdas_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->daqio = pci_resource_start(pcidev, 2);
+ devpriv->BADR3 = pci_resource_start(pcidev, 3);
+ dev->iobase = pci_resource_start(pcidev, 4);
+
+ dev->pacer = comedi_8254_init(devpriv->BADR3 + PCIMDAS_8254_BASE,
+ cb_pcimdas_pacer_clk(dev),
+ I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 6);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if (cb_pcimdas_is_ai_se(dev)) {
+ s->subdev_flags |= SDF_GROUND;
+ s->n_chan = 16;
+ } else {
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = 8;
+ }
+ s->maxdata = 0xffff;
+ s->range_table = cb_pcimdas_is_ai_uni(dev) ? &cb_pcimdas_ai_uni_range
+ : &cb_pcimdas_ai_bip_range;
+ s->insn_read = cb_pcimdas_ai_insn_read;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0xfff;
+ s->range_table = &cb_pcimdas_ao_range;
+ s->insn_write = cb_pcimdas_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[2];
+ ret = subdev_8255_init(dev, s, NULL, PCIMDAS_8255_BASE);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice (main connector) */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = cb_pcimdas_di_insn_bits;
+
+ /* Digital Output subdevice (main connector) */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = cb_pcimdas_do_insn_bits;
+
+ /* Counter subdevice (8254) */
+ s = &dev->subdevices[5];
+ comedi_8254_subdevice_init(s, dev->pacer);
+
+ dev->pacer->insn_config = cb_pcimdas_counter_insn_config;
+
+ /* counters 1 and 2 are used internally for the pacer */
+ comedi_8254_set_busy(dev->pacer, 1, true);
+ comedi_8254_set_busy(dev->pacer, 2, true);
+
+ return 0;
+}
+
+static struct comedi_driver cb_pcimdas_driver = {
+ .driver_name = "cb_pcimdas",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcimdas_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int cb_pcimdas_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcimdas_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcimdas_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0056) }, /* PCIM-DAS1602/16 */
+ { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0115) }, /* PCIe-DAS1602/16 */
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcimdas_pci_table);
+
+static struct pci_driver cb_pcimdas_pci_driver = {
+ .name = "cb_pcimdas",
+ .id_table = cb_pcimdas_pci_table,
+ .probe = cb_pcimdas_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcimdas_driver, cb_pcimdas_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for PCIM-DAS1602/16 and PCIe-DAS1602/16");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/cb_pcimdda.c b/drivers/staging/comedi/drivers/cb_pcimdda.c
new file mode 100644
index 000000000..a4781dbbd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcimdda.c
@@ -0,0 +1,206 @@
+/*
+ comedi/drivers/cb_pcimdda.c
+ Computer Boards PCIM-DDA06-16 Comedi driver
+ Author: Calin Culianu <calin@ajvar.org>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: cb_pcimdda
+Description: Measurement Computing PCIM-DDA06-16
+Devices: [Measurement Computing] PCIM-DDA06-16 (cb_pcimdda)
+Author: Calin Culianu <calin@ajvar.org>
+Updated: Mon, 14 Apr 2008 15:15:51 +0100
+Status: works
+
+All features of the PCIM-DDA06-16 board are supported. This board
+has 6 16-bit AO channels, and the usual 8255 DIO setup. (24 channels,
+configurable in banks of 8 and 4, etc.). This board does not support commands.
+
+The board has a peculiar way of specifying AO gain/range settings -- You have
+1 jumper bank on the card, which either makes all 6 AO channels either
+5 Volt unipolar, 5V bipolar, 10 Volt unipolar or 10V bipolar.
+
+Since there is absolutely _no_ way to tell in software how this jumper is set
+(well, at least according to the rather thin spec. from Measurement Computing
+ that comes with the board), the driver assumes the jumper is at its factory
+default setting of +/-5V.
+
+Also of note is the fact that this board features another jumper, whose
+state is also completely invisible to software. It toggles two possible AO
+output modes on the board:
+
+ - Update Mode: Writing to an AO channel instantaneously updates the actual
+ signal output by the DAC on the board (this is the factory default).
+ - Simultaneous XFER Mode: Writing to an AO channel has no effect until
+ you read from any one of the AO channels. This is useful for loading
+ all 6 AO values, and then reading from any one of the AO channels on the
+ device to instantly update all 6 AO values in unison. Useful for some
+ control apps, I would assume? If your jumper is in this setting, then you
+ need to issue your comedi_data_write()s to load all the values you want,
+ then issue one comedi_data_read() on any channel on the AO subdevice
+ to initiate the simultaneous XFER.
+
+Configuration Options: not applicable, uses PCI auto config
+*/
+
+/*
+ This is a driver for the Computer Boards PCIM-DDA06-16 Analog Output
+ card. This board has a unique register layout and as such probably
+ deserves its own driver file.
+
+ It is theoretically possible to integrate this board into the cb_pcidda
+ file, but since that isn't my code, I didn't want to significantly
+ modify that file to support this board (I thought it impolite to do so).
+
+ At any rate, if you feel ambitious, please feel free to take
+ the code out of this file and combine it with a more unified driver
+ file.
+
+ I would like to thank Timothy Curry <Timothy.Curry@rdec.redstone.army.mil>
+ for lending me a board so that I could write this driver.
+
+ -Calin Culianu <calin@ajvar.org>
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+#include "8255.h"
+
+/* device ids of the cards we support -- currently only 1 card supported */
+#define PCI_ID_PCIM_DDA06_16 0x0053
+
+/*
+ * Register map, 8-bit access only
+ */
+#define PCIMDDA_DA_CHAN(x) (0x00 + (x) * 2)
+#define PCIMDDA_8255_BASE_REG 0x0c
+
+static int cb_pcimdda_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /*
+ * Write the LSB then MSB.
+ *
+ * If the simultaneous xfer mode is selected by the
+ * jumper on the card, a read instruction is needed
+ * in order to initiate the simultaneous transfer.
+ * Otherwise, the DAC will be updated when the MSB
+ * is written.
+ */
+ outb(val & 0x00ff, offset);
+ outb((val >> 8) & 0x00ff, offset + 1);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int cb_pcimdda_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /* Initiate the simultaneous transfer */
+ inw(dev->iobase + PCIMDDA_DA_CHAN(chan));
+
+ return comedi_readback_insn_read(dev, s, insn, data);
+}
+
+static int cb_pcimdda_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 3);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 6;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar5;
+ s->insn_write = cb_pcimdda_ao_insn_write;
+ s->insn_read = cb_pcimdda_ao_insn_read;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[1];
+ /* digital i/o subdevice */
+ ret = subdev_8255_init(dev, s, NULL, PCIMDDA_8255_BASE_REG);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver cb_pcimdda_driver = {
+ .driver_name = "cb_pcimdda",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcimdda_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int cb_pcimdda_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcimdda_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcimdda_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CB, PCI_ID_PCIM_DDA06_16) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcimdda_pci_table);
+
+static struct pci_driver cb_pcimdda_driver_pci_driver = {
+ .name = "cb_pcimdda",
+ .id_table = cb_pcimdda_pci_table,
+ .probe = cb_pcimdda_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcimdda_driver, cb_pcimdda_driver_pci_driver);
+
+MODULE_AUTHOR("Calin A. Culianu <calin@rtlab.org>");
+MODULE_DESCRIPTION("Comedi low-level driver for the Computerboards PCIM-DDA "
+ "series. Currently only supports PCIM-DDA06-16 (which "
+ "also happens to be the only board in this series. :) ) ");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_8254.c b/drivers/staging/comedi/drivers/comedi_8254.c
new file mode 100644
index 000000000..0d5d56b61
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_8254.c
@@ -0,0 +1,664 @@
+/*
+ * comedi_8254.c
+ * Generic 8254 timer/counter support
+ * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on 8253.h and various subdevice implementations in comedi drivers.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Module: comedi_8254
+ * Description: Generic 8254 timer/counter support
+ * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Updated: Thu Jan 8 16:45:45 MST 2015
+ * Status: works
+ *
+ * This module is not used directly by end-users. Rather, it is used by other
+ * drivers to provide support for an 8254 Programmable Interval Timer. These
+ * counters are typically used to generate the pacer clock used for data
+ * acquisition. Some drivers also expose the counters for general purpose use.
+ *
+ * This module provides the following basic functions:
+ *
+ * comedi_8254_init() / comedi_8254_mm_init()
+ * Initializes this module to access the 8254 registers. The _mm version
+ * sets up the module for MMIO register access the other for PIO access.
+ * The pointer returned from these functions is normally stored in the
+ * comedi_device dev->pacer and will be freed by the comedi core during
+ * the driver (*detach). If a driver has multiple 8254 devices, they need
+ * to be stored in the drivers private data and freed when the driver is
+ * detached.
+ *
+ * NOTE: The counters are reset by setting them to I8254_MODE0 as part of
+ * this initialization.
+ *
+ * comedi_8254_set_mode()
+ * Sets a counters operation mode:
+ * I8254_MODE0 Interrupt on terminal count
+ * I8254_MODE1 Hardware retriggerable one-shot
+ * I8254_MODE2 Rate generator
+ * I8254_MODE3 Square wave mode
+ * I8254_MODE4 Software triggered strobe
+ * I8254_MODE5 Hardware triggered strobe (retriggerable)
+ *
+ * In addition I8254_BCD and I8254_BINARY specify the counting mode:
+ * I8254_BCD BCD counting
+ * I8254_BINARY Binary counting
+ *
+ * comedi_8254_write()
+ * Writes an initial value to a counter.
+ *
+ * The largest possible initial count is 0; this is equivalent to 2^16
+ * for binary counting and 10^4 for BCD counting.
+ *
+ * NOTE: The counter does not stop when it reaches zero. In Mode 0, 1, 4,
+ * and 5 the counter "wraps around" to the highest count, either 0xffff
+ * for binary counting or 9999 for BCD counting, and continues counting.
+ * Modes 2 and 3 are periodic; the counter reloads itself with the initial
+ * count and continues counting from there.
+ *
+ * comedi_8254_read()
+ * Reads the current value from a counter.
+ *
+ * comedi_8254_status()
+ * Reads the status of a counter.
+ *
+ * comedi_8254_load()
+ * Sets a counters operation mode and writes the initial value.
+ *
+ * Typically the pacer clock is created by cascading two of the 16-bit counters
+ * to create a 32-bit rate generator (I8254_MODE2). These functions are
+ * provided to handle the cascaded counters:
+ *
+ * comedi_8254_ns_to_timer()
+ * Calculates the divisor value needed for a single counter to generate
+ * ns timing.
+ *
+ * comedi_8254_cascade_ns_to_timer()
+ * Calculates the two divisor values needed to the generate the pacer
+ * clock (in ns).
+ *
+ * comedi_8254_update_divisors()
+ * Transfers the intermediate divisor values to the current divisors.
+ *
+ * comedi_8254_pacer_enable()
+ * Programs the mode of the cascaded counters and writes the current
+ * divisor values.
+ *
+ * To expose the counters as a subdevice for general purpose use the following
+ * functions a provided:
+ *
+ * comedi_8254_subdevice_init()
+ * Initializes a comedi_subdevice to use the 8254 timer.
+ *
+ * comedi_8254_set_busy()
+ * Internally flags a counter as "busy". This is done to protect the
+ * counters that are used for the cascaded 32-bit pacer.
+ *
+ * The subdevice provides (*insn_read) and (*insn_write) operations to read
+ * the current value and write an initial value to a counter. A (*insn_config)
+ * operation is also provided to handle the following comedi instructions:
+ *
+ * INSN_CONFIG_SET_COUNTER_MODE calls comedi_8254_set_mode()
+ * INSN_CONFIG_8254_READ_STATUS calls comedi_8254_status()
+ *
+ * The (*insn_config) member of comedi_8254 can be initialized by the external
+ * driver to handle any additional instructions.
+ *
+ * NOTE: Gate control, clock routing, and any interrupt handling for the
+ * counters is not handled by this module. These features are driver dependent.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include "../comedidev.h"
+
+#include "comedi_8254.h"
+
+static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg)
+{
+ unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
+ unsigned int val;
+
+ switch (i8254->iosize) {
+ default:
+ case I8254_IO8:
+ if (i8254->mmio)
+ val = readb(i8254->mmio + reg_offset);
+ else
+ val = inb(i8254->iobase + reg_offset);
+ break;
+ case I8254_IO16:
+ if (i8254->mmio)
+ val = readw(i8254->mmio + reg_offset);
+ else
+ val = inw(i8254->iobase + reg_offset);
+ break;
+ case I8254_IO32:
+ if (i8254->mmio)
+ val = readl(i8254->mmio + reg_offset);
+ else
+ val = inl(i8254->iobase + reg_offset);
+ break;
+ }
+ return val & 0xff;
+}
+
+static void __i8254_write(struct comedi_8254 *i8254,
+ unsigned int val, unsigned int reg)
+{
+ unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
+
+ switch (i8254->iosize) {
+ default:
+ case I8254_IO8:
+ if (i8254->mmio)
+ writeb(val, i8254->mmio + reg_offset);
+ else
+ outb(val, i8254->iobase + reg_offset);
+ break;
+ case I8254_IO16:
+ if (i8254->mmio)
+ writew(val, i8254->mmio + reg_offset);
+ else
+ outw(val, i8254->iobase + reg_offset);
+ break;
+ case I8254_IO32:
+ if (i8254->mmio)
+ writel(val, i8254->mmio + reg_offset);
+ else
+ outl(val, i8254->iobase + reg_offset);
+ break;
+ }
+}
+
+/**
+ * comedi_8254_status - return the status of a counter
+ * @i8254: comedi_8254 struct for the timer
+ * @counter: the counter number
+ */
+unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter)
+{
+ unsigned int cmd;
+
+ if (counter > 2)
+ return 0;
+
+ cmd = I8254_CTRL_READBACK_STATUS | I8254_CTRL_READBACK_SEL_CTR(counter);
+ __i8254_write(i8254, cmd, I8254_CTRL_REG);
+
+ return __i8254_read(i8254, counter);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_status);
+
+/**
+ * comedi_8254_read - read the current counter value
+ * @i8254: comedi_8254 struct for the timer
+ * @counter: the counter number
+ */
+unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter)
+{
+ unsigned int val;
+
+ if (counter > 2)
+ return 0;
+
+ /* latch counter */
+ __i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH,
+ I8254_CTRL_REG);
+
+ /* read LSB then MSB */
+ val = __i8254_read(i8254, counter);
+ val |= (__i8254_read(i8254, counter) << 8);
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_read);
+
+/**
+ * comedi_8254_write - load a 16-bit initial counter value
+ * @i8254: comedi_8254 struct for the timer
+ * @counter: the counter number
+ * @val: the initial value
+ */
+void comedi_8254_write(struct comedi_8254 *i8254,
+ unsigned int counter, unsigned int val)
+{
+ unsigned int byte;
+
+ if (counter > 2)
+ return;
+ if (val > 0xffff)
+ return;
+
+ /* load LSB then MSB */
+ byte = val & 0xff;
+ __i8254_write(i8254, byte, counter);
+ byte = (val >> 8) & 0xff;
+ __i8254_write(i8254, byte, counter);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_write);
+
+/**
+ * comedi_8254_set_mode - set the mode of a counter
+ * @i8254: comedi_8254 struct for the timer
+ * @counter: the counter number
+ * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY
+ */
+int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter,
+ unsigned int mode)
+{
+ unsigned int byte;
+
+ if (counter > 2)
+ return -EINVAL;
+ if (mode > (I8254_MODE5 | I8254_BCD))
+ return -EINVAL;
+
+ byte = I8254_CTRL_SEL_CTR(counter) | /* select counter */
+ I8254_CTRL_LSB_MSB | /* load LSB then MSB */
+ mode; /* mode and BCD|binary */
+ __i8254_write(i8254, byte, I8254_CTRL_REG);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_set_mode);
+
+/**
+ * comedi_8254_load - program the mode and initial count of a counter
+ * @i8254: comedi_8254 struct for the timer
+ * @counter: the counter number
+ * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY
+ * @val: the initial value
+ */
+int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter,
+ unsigned int val, unsigned int mode)
+{
+ if (counter > 2)
+ return -EINVAL;
+ if (val > 0xffff)
+ return -EINVAL;
+ if (mode > (I8254_MODE5 | I8254_BCD))
+ return -EINVAL;
+
+ comedi_8254_set_mode(i8254, counter, mode);
+ comedi_8254_write(i8254, counter, val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_load);
+
+/**
+ * comedi_8254_pacer_enable - set the mode and load the cascaded counters
+ * @i8254: comedi_8254 struct for the timer
+ * @counter1: the counter number for the first divisor
+ * @counter2: the counter number for the second divisor
+ * @enable: flag to enable (load) the counters
+ */
+void comedi_8254_pacer_enable(struct comedi_8254 *i8254,
+ unsigned int counter1,
+ unsigned int counter2,
+ bool enable)
+{
+ unsigned int mode;
+
+ if (counter1 > 2 || counter2 > 2 || counter1 == counter2)
+ return;
+
+ if (enable)
+ mode = I8254_MODE2 | I8254_BINARY;
+ else
+ mode = I8254_MODE0 | I8254_BINARY;
+
+ comedi_8254_set_mode(i8254, counter1, mode);
+ comedi_8254_set_mode(i8254, counter2, mode);
+
+ if (enable) {
+ /*
+ * Divisors are loaded second counter then first counter to
+ * avoid possible issues with the first counter expiring
+ * before the second counter is loaded.
+ */
+ comedi_8254_write(i8254, counter2, i8254->divisor2);
+ comedi_8254_write(i8254, counter1, i8254->divisor1);
+ }
+}
+EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable);
+
+/**
+ * comedi_8254_update_divisors - update the divisors for the cascaded counters
+ * @i8254: comedi_8254 struct for the timer
+ */
+void comedi_8254_update_divisors(struct comedi_8254 *i8254)
+{
+ /* masking is done since counter maps zero to 0x10000 */
+ i8254->divisor = i8254->next_div & 0xffff;
+ i8254->divisor1 = i8254->next_div1 & 0xffff;
+ i8254->divisor2 = i8254->next_div2 & 0xffff;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_update_divisors);
+
+/**
+ * comedi_8254_cascade_ns_to_timer - calculate the cascaded divisor values
+ * @i8254: comedi_8254 struct for the timer
+ * @nanosec: the desired ns time
+ * @flags: comedi_cmd flags
+ */
+void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254,
+ unsigned int *nanosec,
+ unsigned int flags)
+{
+ unsigned int d1 = i8254->next_div1 ? i8254->next_div1 : I8254_MAX_COUNT;
+ unsigned int d2 = i8254->next_div2 ? i8254->next_div2 : I8254_MAX_COUNT;
+ unsigned int div = d1 * d2;
+ unsigned int ns_lub = 0xffffffff;
+ unsigned int ns_glb = 0;
+ unsigned int d1_lub = 0;
+ unsigned int d1_glb = 0;
+ unsigned int d2_lub = 0;
+ unsigned int d2_glb = 0;
+ unsigned int start;
+ unsigned int ns;
+ unsigned int ns_low;
+ unsigned int ns_high;
+
+ /* exit early if everything is already correct */
+ if (div * i8254->osc_base == *nanosec &&
+ d1 > 1 && d1 <= I8254_MAX_COUNT &&
+ d2 > 1 && d2 <= I8254_MAX_COUNT &&
+ /* check for overflow */
+ div > d1 && div > d2 &&
+ div * i8254->osc_base > div &&
+ div * i8254->osc_base > i8254->osc_base)
+ return;
+
+ div = *nanosec / i8254->osc_base;
+ d2 = I8254_MAX_COUNT;
+ start = div / d2;
+ if (start < 2)
+ start = 2;
+ for (d1 = start; d1 <= div / d1 + 1 && d1 <= I8254_MAX_COUNT; d1++) {
+ for (d2 = div / d1;
+ d1 * d2 <= div + d1 + 1 && d2 <= I8254_MAX_COUNT; d2++) {
+ ns = i8254->osc_base * d1 * d2;
+ if (ns <= *nanosec && ns > ns_glb) {
+ ns_glb = ns;
+ d1_glb = d1;
+ d2_glb = d2;
+ }
+ if (ns >= *nanosec && ns < ns_lub) {
+ ns_lub = ns;
+ d1_lub = d1;
+ d2_lub = d2;
+ }
+ }
+ }
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ ns_high = d1_lub * d2_lub * i8254->osc_base;
+ ns_low = d1_glb * d2_glb * i8254->osc_base;
+ if (ns_high - *nanosec < *nanosec - ns_low) {
+ d1 = d1_lub;
+ d2 = d2_lub;
+ } else {
+ d1 = d1_glb;
+ d2 = d2_glb;
+ }
+ break;
+ case CMDF_ROUND_UP:
+ d1 = d1_lub;
+ d2 = d2_lub;
+ break;
+ case CMDF_ROUND_DOWN:
+ d1 = d1_glb;
+ d2 = d2_glb;
+ break;
+ }
+
+ *nanosec = d1 * d2 * i8254->osc_base;
+ i8254->next_div1 = d1;
+ i8254->next_div2 = d2;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer);
+
+/**
+ * comedi_8254_ns_to_timer - calculate the divisor value for nanosec timing
+ * @i8254: comedi_8254 struct for the timer
+ * @nanosec: the desired ns time
+ * @flags: comedi_cmd flags
+ */
+void comedi_8254_ns_to_timer(struct comedi_8254 *i8254,
+ unsigned int *nanosec, unsigned int flags)
+{
+ unsigned int divisor;
+
+ switch (flags & CMDF_ROUND_MASK) {
+ default:
+ case CMDF_ROUND_NEAREST:
+ divisor = DIV_ROUND_CLOSEST(*nanosec, i8254->osc_base);
+ break;
+ case CMDF_ROUND_UP:
+ divisor = DIV_ROUND_UP(*nanosec, i8254->osc_base);
+ break;
+ case CMDF_ROUND_DOWN:
+ divisor = *nanosec / i8254->osc_base;
+ break;
+ }
+ if (divisor < 2)
+ divisor = 2;
+ if (divisor > I8254_MAX_COUNT)
+ divisor = I8254_MAX_COUNT;
+
+ *nanosec = divisor * i8254->osc_base;
+ i8254->next_div = divisor;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer);
+
+/**
+ * comedi_8254_set_busy - set/clear the "busy" flag for a given counter
+ * @i8254: comedi_8254 struct for the timer
+ * @counter: the counter number
+ * @busy: set/clear flag
+ */
+void comedi_8254_set_busy(struct comedi_8254 *i8254,
+ unsigned int counter, bool busy)
+{
+ if (counter < 3)
+ i8254->busy[counter] = busy;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_set_busy);
+
+static int comedi_8254_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct comedi_8254 *i8254 = s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ if (i8254->busy[chan])
+ return -EBUSY;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = comedi_8254_read(i8254, chan);
+
+ return insn->n;
+}
+
+static int comedi_8254_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct comedi_8254 *i8254 = s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ if (i8254->busy[chan])
+ return -EBUSY;
+
+ if (insn->n)
+ comedi_8254_write(i8254, chan, data[insn->n - 1]);
+
+ return insn->n;
+}
+
+static int comedi_8254_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct comedi_8254 *i8254 = s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret;
+
+ if (i8254->busy[chan])
+ return -EBUSY;
+
+ switch (data[0]) {
+ case INSN_CONFIG_RESET:
+ ret = comedi_8254_set_mode(i8254, chan,
+ I8254_MODE0 | I8254_BINARY);
+ if (ret)
+ return ret;
+ break;
+ case INSN_CONFIG_SET_COUNTER_MODE:
+ ret = comedi_8254_set_mode(i8254, chan, data[1]);
+ if (ret)
+ return ret;
+ break;
+ case INSN_CONFIG_8254_READ_STATUS:
+ data[1] = comedi_8254_status(i8254, chan);
+ break;
+ default:
+ /*
+ * If available, call the driver provided (*insn_config)
+ * to handle any driver implemented instructions.
+ */
+ if (i8254->insn_config)
+ return i8254->insn_config(dev, s, insn, data);
+
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+/**
+ * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer
+ * @s: comedi_subdevice struct
+ */
+void comedi_8254_subdevice_init(struct comedi_subdevice *s,
+ struct comedi_8254 *i8254)
+{
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 3;
+ s->maxdata = 0xffff;
+ s->range_table = &range_unknown;
+ s->insn_read = comedi_8254_insn_read;
+ s->insn_write = comedi_8254_insn_write;
+ s->insn_config = comedi_8254_insn_config;
+
+ s->private = i8254;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init);
+
+static struct comedi_8254 *__i8254_init(unsigned long iobase,
+ void __iomem *mmio,
+ unsigned int osc_base,
+ unsigned int iosize,
+ unsigned int regshift)
+{
+ struct comedi_8254 *i8254;
+ int i;
+
+ /* sanity check that the iosize is valid */
+ if (!(iosize == I8254_IO8 || iosize == I8254_IO16 ||
+ iosize == I8254_IO32))
+ return NULL;
+
+ i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL);
+ if (!i8254)
+ return NULL;
+
+ i8254->iobase = iobase;
+ i8254->mmio = mmio;
+ i8254->iosize = iosize;
+ i8254->regshift = regshift;
+
+ /* default osc_base to the max speed of a generic 8254 timer */
+ i8254->osc_base = osc_base ? osc_base : I8254_OSC_BASE_10MHZ;
+
+ /* reset all the counters by setting them to I8254_MODE0 */
+ for (i = 0; i < 3; i++)
+ comedi_8254_set_mode(i8254, i, I8254_MODE0 | I8254_BINARY);
+
+ return i8254;
+}
+
+/**
+ * comedi_8254_init - allocate and initialize the 8254 device for pio access
+ * @mmio: port I/O base address
+ * @osc_base: base time of the counter in ns
+ * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
+ * @iosize: I/O register size
+ * @regshift: register gap shift
+ */
+struct comedi_8254 *comedi_8254_init(unsigned long iobase,
+ unsigned int osc_base,
+ unsigned int iosize,
+ unsigned int regshift)
+{
+ return __i8254_init(iobase, NULL, osc_base, iosize, regshift);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_init);
+
+/**
+ * comedi_8254_mm_init - allocate and initialize the 8254 device for mmio access
+ * @mmio: memory mapped I/O base address
+ * @osc_base: base time of the counter in ns
+ * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
+ * @iosize: I/O register size
+ * @regshift: register gap shift
+ */
+struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio,
+ unsigned int osc_base,
+ unsigned int iosize,
+ unsigned int regshift)
+{
+ return __i8254_init(0, mmio, osc_base, iosize, regshift);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_mm_init);
+
+static int __init comedi_8254_module_init(void)
+{
+ return 0;
+}
+module_init(comedi_8254_module_init);
+
+static void __exit comedi_8254_module_exit(void)
+{
+}
+module_exit(comedi_8254_module_exit);
+
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_8254.h b/drivers/staging/comedi/drivers/comedi_8254.h
new file mode 100644
index 000000000..d89f6d94f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_8254.h
@@ -0,0 +1,133 @@
+/*
+ * comedi_8254.h
+ * Generic 8254 timer/counter support
+ * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _COMEDI_8254_H
+#define _COMEDI_8254_H
+
+/*
+ * Common oscillator base values in nanoseconds
+ */
+#define I8254_OSC_BASE_10MHZ 100
+#define I8254_OSC_BASE_5MHZ 200
+#define I8254_OSC_BASE_4MHZ 250
+#define I8254_OSC_BASE_2MHZ 500
+#define I8254_OSC_BASE_1MHZ 1000
+#define I8254_OSC_BASE_100KHZ 10000
+#define I8254_OSC_BASE_10KHZ 100000
+#define I8254_OSC_BASE_1KHZ 1000000
+
+/*
+ * I/O access size used to read/write registers
+ */
+#define I8254_IO8 1
+#define I8254_IO16 2
+#define I8254_IO32 4
+
+/*
+ * Register map for generic 8254 timer (I8254_IO8 with 0 regshift)
+ */
+#define I8254_COUNTER0_REG 0x00
+#define I8254_COUNTER1_REG 0x01
+#define I8254_COUNTER2_REG 0x02
+#define I8254_CTRL_REG 0x03
+#define I8254_CTRL_SEL_CTR(x) ((x) << 6)
+#define I8254_CTRL_READBACK_COUNT ((3 << 6) | (1 << 4))
+#define I8254_CTRL_READBACK_STATUS ((3 << 6) | (1 << 5))
+#define I8254_CTRL_READBACK_SEL_CTR(x) (2 << (x))
+#define I8254_CTRL_LATCH (0 << 4)
+#define I8254_CTRL_LSB_ONLY (1 << 4)
+#define I8254_CTRL_MSB_ONLY (2 << 4)
+#define I8254_CTRL_LSB_MSB (3 << 4)
+
+/* counter maps zero to 0x10000 */
+#define I8254_MAX_COUNT 0x10000
+
+/**
+ * struct comedi_8254 - private data used by this module
+ * @iobase: PIO base address of the registers (in/out)
+ * @mmio: MMIO base address of the registers (read/write)
+ * @iosize: I/O size used to access the registers (b/w/l)
+ * @regshift: register gap shift
+ * @osc_base: cascaded oscillator speed in ns
+ * @divisor: divisor for single counter
+ * @divisor1: divisor loaded into first cascaded counter
+ * @divisor2: divisor loaded into second cascaded counter
+ * #next_div: next divisor for single counter
+ * @next_div1: next divisor to use for first cascaded counter
+ * @next_div2: next divisor to use for second cascaded counter
+ * @clock_src; current clock source for each counter (driver specific)
+ * @gate_src; current gate source for each counter (driver specific)
+ * @busy: flags used to indicate that a counter is "busy"
+ * @insn_config: driver specific (*insn_config) callback
+ */
+struct comedi_8254 {
+ unsigned long iobase;
+ void __iomem *mmio;
+ unsigned int iosize;
+ unsigned int regshift;
+ unsigned int osc_base;
+ unsigned int divisor;
+ unsigned int divisor1;
+ unsigned int divisor2;
+ unsigned int next_div;
+ unsigned int next_div1;
+ unsigned int next_div2;
+ unsigned int clock_src[3];
+ unsigned int gate_src[3];
+ bool busy[3];
+
+ int (*insn_config)(struct comedi_device *, struct comedi_subdevice *s,
+ struct comedi_insn *, unsigned int *data);
+};
+
+unsigned int comedi_8254_status(struct comedi_8254 *, unsigned int counter);
+unsigned int comedi_8254_read(struct comedi_8254 *, unsigned int counter);
+void comedi_8254_write(struct comedi_8254 *,
+ unsigned int counter, unsigned int val);
+
+int comedi_8254_set_mode(struct comedi_8254 *,
+ unsigned int counter, unsigned int mode);
+int comedi_8254_load(struct comedi_8254 *,
+ unsigned int counter, unsigned int val, unsigned int mode);
+
+void comedi_8254_pacer_enable(struct comedi_8254 *,
+ unsigned int counter1, unsigned int counter2,
+ bool enable);
+void comedi_8254_update_divisors(struct comedi_8254 *);
+void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *,
+ unsigned int *nanosec, unsigned int flags);
+void comedi_8254_ns_to_timer(struct comedi_8254 *,
+ unsigned int *nanosec, unsigned int flags);
+
+void comedi_8254_set_busy(struct comedi_8254 *,
+ unsigned int counter, bool busy);
+
+void comedi_8254_subdevice_init(struct comedi_subdevice *,
+ struct comedi_8254 *);
+
+struct comedi_8254 *comedi_8254_init(unsigned long iobase,
+ unsigned int osc_base,
+ unsigned int iosize,
+ unsigned int regshift);
+struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio,
+ unsigned int osc_base,
+ unsigned int iosize,
+ unsigned int regshift);
+
+#endif /* _COMEDI_8254_H */
diff --git a/drivers/staging/comedi/drivers/comedi_bond.c b/drivers/staging/comedi/drivers/comedi_bond.c
new file mode 100644
index 000000000..96db0c268
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_bond.c
@@ -0,0 +1,355 @@
+/*
+ * comedi_bond.c
+ * A Comedi driver to 'bond' or merge multiple drivers and devices as one.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2005 Calin A. Culianu <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: comedi_bond
+ * Description: A driver to 'bond' (merge) multiple subdevices from multiple
+ * devices together as one.
+ * Devices:
+ * Author: ds
+ * Updated: Mon, 10 Oct 00:18:25 -0500
+ * Status: works
+ *
+ * This driver allows you to 'bond' (merge) multiple comedi subdevices
+ * (coming from possibly difference boards and/or drivers) together. For
+ * example, if you had a board with 2 different DIO subdevices, and
+ * another with 1 DIO subdevice, you could 'bond' them with this driver
+ * so that they look like one big fat DIO subdevice. This makes writing
+ * applications slightly easier as you don't have to worry about managing
+ * different subdevices in the application -- you just worry about
+ * indexing one linear array of channel id's.
+ *
+ * Right now only DIO subdevices are supported as that's the personal itch
+ * I am scratching with this driver. If you want to add support for AI and AO
+ * subdevs, go right on ahead and do so!
+ *
+ * Commands aren't supported -- although it would be cool if they were.
+ *
+ * Configuration Options:
+ * List of comedi-minors to bond. All subdevices of the same type
+ * within each minor will be concatenated together in the order given here.
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include "../comedi.h"
+#include "../comedilib.h"
+#include "../comedidev.h"
+
+struct bonded_device {
+ struct comedi_device *dev;
+ unsigned minor;
+ unsigned subdev;
+ unsigned nchans;
+};
+
+struct comedi_bond_private {
+ char name[256];
+ struct bonded_device **devs;
+ unsigned ndevs;
+ unsigned nchans;
+};
+
+static int bonding_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+ unsigned int n_left, n_done, base_chan;
+ unsigned int write_mask, data_bits;
+ struct bonded_device **devs;
+
+ write_mask = data[0];
+ data_bits = data[1];
+ base_chan = CR_CHAN(insn->chanspec);
+ /* do a maximum of 32 channels, starting from base_chan. */
+ n_left = devpriv->nchans - base_chan;
+ if (n_left > 32)
+ n_left = 32;
+
+ n_done = 0;
+ devs = devpriv->devs;
+ do {
+ struct bonded_device *bdev = *devs++;
+
+ if (base_chan < bdev->nchans) {
+ /* base channel falls within bonded device */
+ unsigned int b_chans, b_mask, b_write_mask, b_data_bits;
+ int ret;
+
+ /*
+ * Get num channels to do for bonded device and set
+ * up mask and data bits for bonded device.
+ */
+ b_chans = bdev->nchans - base_chan;
+ if (b_chans > n_left)
+ b_chans = n_left;
+ b_mask = (1U << b_chans) - 1;
+ b_write_mask = (write_mask >> n_done) & b_mask;
+ b_data_bits = (data_bits >> n_done) & b_mask;
+ /* Read/Write the new digital lines. */
+ ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev,
+ b_write_mask, &b_data_bits,
+ base_chan);
+ if (ret < 0)
+ return ret;
+ /* Place read bits into data[1]. */
+ data[1] &= ~(b_mask << n_done);
+ data[1] |= (b_data_bits & b_mask) << n_done;
+ /*
+ * Set up for following bonded device (if still have
+ * channels to read/write).
+ */
+ base_chan = 0;
+ n_done += b_chans;
+ n_left -= b_chans;
+ } else {
+ /* Skip bonded devices before base channel. */
+ base_chan -= bdev->nchans;
+ }
+ } while (n_left);
+
+ return insn->n;
+}
+
+static int bonding_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ struct bonded_device *bdev;
+ struct bonded_device **devs;
+
+ /*
+ * Locate bonded subdevice and adjust channel.
+ */
+ devs = devpriv->devs;
+ for (bdev = *devs++; chan >= bdev->nchans; bdev = *devs++)
+ chan -= bdev->nchans;
+
+ /*
+ * The input or output configuration of each digital line is
+ * configured by a special insn_config instruction. chanspec
+ * contains the channel to be changed, and data[0] contains the
+ * configuration instruction INSN_CONFIG_DIO_OUTPUT,
+ * INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_QUERY.
+ *
+ * Note that INSN_CONFIG_DIO_OUTPUT == COMEDI_OUTPUT,
+ * and INSN_CONFIG_DIO_INPUT == COMEDI_INPUT. This is deliberate ;)
+ */
+ switch (data[0]) {
+ case INSN_CONFIG_DIO_OUTPUT:
+ case INSN_CONFIG_DIO_INPUT:
+ ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, data[0]);
+ break;
+ case INSN_CONFIG_DIO_QUERY:
+ ret = comedi_dio_get_config(bdev->dev, bdev->subdev, chan,
+ &data[1]);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ if (ret >= 0)
+ ret = insn->n;
+ return ret;
+}
+
+static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+ DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS);
+ int i;
+
+ memset(&devs_opened, 0, sizeof(devs_opened));
+ devpriv->name[0] = 0;
+ /*
+ * Loop through all comedi devices specified on the command-line,
+ * building our device list.
+ */
+ for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
+ char file[sizeof("/dev/comediXXXXXX")];
+ int minor = it->options[i];
+ struct comedi_device *d;
+ int sdev = -1, nchans;
+ struct bonded_device *bdev;
+ struct bonded_device **devs;
+
+ if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
+ dev_err(dev->class_dev,
+ "Minor %d is invalid!\n", minor);
+ return -EINVAL;
+ }
+ if (minor == dev->minor) {
+ dev_err(dev->class_dev,
+ "Cannot bond this driver to itself!\n");
+ return -EINVAL;
+ }
+ if (test_and_set_bit(minor, devs_opened)) {
+ dev_err(dev->class_dev,
+ "Minor %d specified more than once!\n", minor);
+ return -EINVAL;
+ }
+
+ snprintf(file, sizeof(file), "/dev/comedi%d", minor);
+ file[sizeof(file) - 1] = 0;
+
+ d = comedi_open(file);
+
+ if (!d) {
+ dev_err(dev->class_dev,
+ "Minor %u could not be opened\n", minor);
+ return -ENODEV;
+ }
+
+ /* Do DIO, as that's all we support now.. */
+ while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
+ sdev + 1)) > -1) {
+ nchans = comedi_get_n_channels(d, sdev);
+ if (nchans <= 0) {
+ dev_err(dev->class_dev,
+ "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
+ nchans, minor, sdev);
+ return -EINVAL;
+ }
+ bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
+ if (!bdev)
+ return -ENOMEM;
+
+ bdev->dev = d;
+ bdev->minor = minor;
+ bdev->subdev = sdev;
+ bdev->nchans = nchans;
+ devpriv->nchans += nchans;
+
+ /*
+ * Now put bdev pointer at end of devpriv->devs array
+ * list..
+ */
+
+ /* ergh.. ugly.. we need to realloc :( */
+ devs = krealloc(devpriv->devs,
+ (devpriv->ndevs + 1) * sizeof(*devs),
+ GFP_KERNEL);
+ if (!devs) {
+ dev_err(dev->class_dev,
+ "Could not allocate memory. Out of memory?\n");
+ kfree(bdev);
+ return -ENOMEM;
+ }
+ devpriv->devs = devs;
+ devpriv->devs[devpriv->ndevs++] = bdev;
+ {
+ /* Append dev:subdev to devpriv->name */
+ char buf[20];
+
+ snprintf(buf, sizeof(buf), "%u:%u ",
+ bdev->minor, bdev->subdev);
+ strlcat(devpriv->name, buf,
+ sizeof(devpriv->name));
+ }
+ }
+ }
+
+ if (!devpriv->nchans) {
+ dev_err(dev->class_dev, "No channels found!\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bonding_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_bond_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /*
+ * Setup our bonding from config params.. sets up our private struct..
+ */
+ ret = do_dev_config(dev, it);
+ if (ret)
+ return ret;
+
+ dev->board_name = devpriv->name;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = devpriv->nchans;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = bonding_dio_insn_bits;
+ s->insn_config = bonding_dio_insn_config;
+
+ dev_info(dev->class_dev,
+ "%s: %s attached, %u channels from %u devices\n",
+ dev->driver->driver_name, dev->board_name,
+ devpriv->nchans, devpriv->ndevs);
+
+ return 0;
+}
+
+static void bonding_detach(struct comedi_device *dev)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+
+ if (devpriv && devpriv->devs) {
+ DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS);
+
+ memset(&devs_closed, 0, sizeof(devs_closed));
+ while (devpriv->ndevs--) {
+ struct bonded_device *bdev;
+
+ bdev = devpriv->devs[devpriv->ndevs];
+ if (!bdev)
+ continue;
+ if (!test_and_set_bit(bdev->minor, devs_closed))
+ comedi_close(bdev->dev);
+ kfree(bdev);
+ }
+ kfree(devpriv->devs);
+ devpriv->devs = NULL;
+ }
+}
+
+static struct comedi_driver bonding_driver = {
+ .driver_name = "comedi_bond",
+ .module = THIS_MODULE,
+ .attach = bonding_attach,
+ .detach = bonding_detach,
+};
+module_comedi_driver(bonding_driver);
+
+MODULE_AUTHOR("Calin A. Culianu");
+MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_isadma.c b/drivers/staging/comedi/drivers/comedi_isadma.c
new file mode 100644
index 000000000..6ba71d114
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_isadma.c
@@ -0,0 +1,264 @@
+/*
+ * COMEDI ISA DMA support functions
+ * Copyright (c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <asm/dma.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+
+/**
+ * comedi_isadma_program - program and enable an ISA DMA transfer
+ * @desc: the ISA DMA cookie to program and enable
+ */
+void comedi_isadma_program(struct comedi_isadma_desc *desc)
+{
+ unsigned long flags;
+
+ flags = claim_dma_lock();
+ clear_dma_ff(desc->chan);
+ set_dma_mode(desc->chan, desc->mode);
+ set_dma_addr(desc->chan, desc->hw_addr);
+ set_dma_count(desc->chan, desc->size);
+ enable_dma(desc->chan);
+ release_dma_lock(flags);
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_program);
+
+/**
+ * comedi_isadma_disable - disable the ISA DMA channel
+ * @dma_chan: the DMA channel to disable
+ *
+ * Returns the residue (remaining bytes) left in the DMA transfer.
+ */
+unsigned int comedi_isadma_disable(unsigned int dma_chan)
+{
+ unsigned long flags;
+ unsigned int residue;
+
+ flags = claim_dma_lock();
+ disable_dma(dma_chan);
+ residue = get_dma_residue(dma_chan);
+ release_dma_lock(flags);
+
+ return residue;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_disable);
+
+/**
+ * comedi_isadma_disable_on_sample - disable the ISA DMA channel
+ * @dma_chan: the DMA channel to disable
+ * @size: the sample size (in bytes)
+ *
+ * Returns the residue (remaining bytes) left in the DMA transfer.
+ */
+unsigned int comedi_isadma_disable_on_sample(unsigned int dma_chan,
+ unsigned int size)
+{
+ int stalled = 0;
+ unsigned long flags;
+ unsigned int residue;
+ unsigned int new_residue;
+
+ residue = comedi_isadma_disable(dma_chan);
+ while (residue % size) {
+ /* residue is a partial sample, enable DMA to allow more data */
+ flags = claim_dma_lock();
+ enable_dma(dma_chan);
+ release_dma_lock(flags);
+
+ udelay(2);
+ new_residue = comedi_isadma_disable(dma_chan);
+
+ /* is DMA stalled? */
+ if (new_residue == residue) {
+ stalled++;
+ if (stalled > 10)
+ break;
+ } else {
+ residue = new_residue;
+ stalled = 0;
+ }
+ }
+ return residue;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_disable_on_sample);
+
+/**
+ * comedi_isadma_poll - poll the current DMA transfer
+ * @dma: the ISA DMA to poll
+ *
+ * Returns the position (in bytes) of the current DMA transfer.
+ */
+unsigned int comedi_isadma_poll(struct comedi_isadma *dma)
+{
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned long flags;
+ unsigned int result;
+ unsigned int result1;
+
+ flags = claim_dma_lock();
+ clear_dma_ff(desc->chan);
+ if (!isa_dma_bridge_buggy)
+ disable_dma(desc->chan);
+ result = get_dma_residue(desc->chan);
+ /*
+ * Read the counter again and choose higher value in order to
+ * avoid reading during counter lower byte roll over if the
+ * isa_dma_bridge_buggy is set.
+ */
+ result1 = get_dma_residue(desc->chan);
+ if (!isa_dma_bridge_buggy)
+ enable_dma(desc->chan);
+ release_dma_lock(flags);
+
+ if (result < result1)
+ result = result1;
+ if (result >= desc->size || result == 0)
+ return 0;
+ else
+ return desc->size - result;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_poll);
+
+/**
+ * comedi_isadma_set_mode - set the ISA DMA transfer direction
+ * @desc: the ISA DMA cookie to set
+ * @dma_dir: the DMA direction
+ */
+void comedi_isadma_set_mode(struct comedi_isadma_desc *desc, char dma_dir)
+{
+ desc->mode = (dma_dir == COMEDI_ISADMA_READ) ? DMA_MODE_READ
+ : DMA_MODE_WRITE;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_set_mode);
+
+/**
+ * comedi_isadma_alloc - allocate and initialize the ISA DMA
+ * @dev: comedi_device struct
+ * @n_desc: the number of cookies to allocate
+ * @dma_chan: DMA channel for the first cookie
+ * @dma_chan2: DMA channel for the second cookie
+ * @maxsize: the size of the buffer to allocate for each cookie
+ * @dma_dir: the DMA direction
+ *
+ * Returns the allocated and initialized ISA DMA or NULL if anything fails.
+ */
+struct comedi_isadma *comedi_isadma_alloc(struct comedi_device *dev,
+ int n_desc, unsigned int dma_chan1,
+ unsigned int dma_chan2,
+ unsigned int maxsize, char dma_dir)
+{
+ struct comedi_isadma *dma = NULL;
+ struct comedi_isadma_desc *desc;
+ unsigned int dma_chans[2];
+ int i;
+
+ if (n_desc < 1 || n_desc > 2)
+ goto no_dma;
+
+ dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+ if (!dma)
+ goto no_dma;
+
+ desc = kcalloc(n_desc, sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ goto no_dma;
+ dma->desc = desc;
+ dma->n_desc = n_desc;
+
+ dma_chans[0] = dma_chan1;
+ if (dma_chan2 == 0 || dma_chan2 == dma_chan1)
+ dma_chans[1] = dma_chan1;
+ else
+ dma_chans[1] = dma_chan2;
+
+ if (request_dma(dma_chans[0], dev->board_name))
+ goto no_dma;
+ dma->chan = dma_chans[0];
+ if (dma_chans[1] != dma_chans[0]) {
+ if (request_dma(dma_chans[1], dev->board_name))
+ goto no_dma;
+ }
+ dma->chan2 = dma_chans[1];
+
+ for (i = 0; i < n_desc; i++) {
+ desc = &dma->desc[i];
+ desc->chan = dma_chans[i];
+ desc->maxsize = maxsize;
+ desc->virt_addr = dma_alloc_coherent(NULL, desc->maxsize,
+ &desc->hw_addr,
+ GFP_KERNEL);
+ if (!desc->virt_addr)
+ goto no_dma;
+ comedi_isadma_set_mode(desc, dma_dir);
+ }
+
+ return dma;
+
+no_dma:
+ comedi_isadma_free(dma);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_alloc);
+
+/**
+ * comedi_isadma_free - free the ISA DMA
+ * @dma: the ISA DMA to free
+ */
+void comedi_isadma_free(struct comedi_isadma *dma)
+{
+ struct comedi_isadma_desc *desc;
+ int i;
+
+ if (!dma)
+ return;
+
+ if (dma->desc) {
+ for (i = 0; i < dma->n_desc; i++) {
+ desc = &dma->desc[i];
+ if (desc->virt_addr)
+ dma_free_coherent(NULL, desc->maxsize,
+ desc->virt_addr,
+ desc->hw_addr);
+ }
+ kfree(dma->desc);
+ }
+ if (dma->chan2 && dma->chan2 != dma->chan)
+ free_dma(dma->chan2);
+ if (dma->chan)
+ free_dma(dma->chan);
+ kfree(dma);
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_free);
+
+static int __init comedi_isadma_init(void)
+{
+ return 0;
+}
+module_init(comedi_isadma_init);
+
+static void __exit comedi_isadma_exit(void)
+{
+}
+module_exit(comedi_isadma_exit);
+
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("Comedi ISA DMA support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_isadma.h b/drivers/staging/comedi/drivers/comedi_isadma.h
new file mode 100644
index 000000000..c7c524faf
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_isadma.h
@@ -0,0 +1,116 @@
+/*
+ * COMEDI ISA DMA support functions
+ * Copyright (c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _COMEDI_ISADMA_H
+#define _COMEDI_ISADMA_H
+
+/*
+ * These are used to avoid issues when <asm/dma.h> and the DMA_MODE_
+ * defines are not available.
+ */
+#define COMEDI_ISADMA_READ 0
+#define COMEDI_ISADMA_WRITE 1
+
+/**
+ * struct comedi_isadma_desc - cookie for ISA DMA
+ * @virt_addr: virtual address of buffer
+ * @hw_addr: hardware (bus) address of buffer
+ * @chan: DMA channel
+ * @maxsize: allocated size of buffer (in bytes)
+ * @size: transfer size (in bytes)
+ * @mode: DMA_MODE_READ or DMA_MODE_WRITE
+ */
+struct comedi_isadma_desc {
+ void *virt_addr;
+ dma_addr_t hw_addr;
+ unsigned int chan;
+ unsigned int maxsize;
+ unsigned int size;
+ char mode;
+};
+
+/**
+ * struct comedi_isadma - ISA DMA data
+ * @desc: cookie for each DMA buffer
+ * @n_desc: the number of cookies
+ * @cur_dma: the current cookie in use
+ * @chan: the first DMA channel requested
+ * @chan2: the second DMA channel requested
+ */
+struct comedi_isadma {
+ struct comedi_isadma_desc *desc;
+ int n_desc;
+ int cur_dma;
+ unsigned int chan;
+ unsigned int chan2;
+};
+
+#if IS_ENABLED(CONFIG_ISA_DMA_API)
+
+void comedi_isadma_program(struct comedi_isadma_desc *);
+unsigned int comedi_isadma_disable(unsigned int dma_chan);
+unsigned int comedi_isadma_disable_on_sample(unsigned int dma_chan,
+ unsigned int size);
+unsigned int comedi_isadma_poll(struct comedi_isadma *);
+void comedi_isadma_set_mode(struct comedi_isadma_desc *, char dma_dir);
+
+struct comedi_isadma *comedi_isadma_alloc(struct comedi_device *,
+ int n_desc, unsigned int dma_chan1,
+ unsigned int dma_chan2,
+ unsigned int maxsize, char dma_dir);
+void comedi_isadma_free(struct comedi_isadma *);
+
+#else /* !IS_ENABLED(CONFIG_ISA_DMA_API) */
+
+static inline void comedi_isadma_program(struct comedi_isadma_desc *desc)
+{
+}
+
+static inline unsigned int comedi_isadma_disable(unsigned int dma_chan)
+{
+ return 0;
+}
+
+static inline unsigned int
+comedi_isadma_disable_on_sample(unsigned int dma_chan, unsigned int size)
+{
+ return 0;
+}
+
+static inline unsigned int comedi_isadma_poll(struct comedi_isadma *dma)
+{
+ return 0;
+}
+
+static inline void comedi_isadma_set_mode(struct comedi_isadma_desc *desc,
+ char dma_dir)
+{
+}
+
+static inline struct comedi_isadma *
+comedi_isadma_alloc(struct comedi_device *dev, int n_desc,
+ unsigned int dma_chan1, unsigned int dma_chan2,
+ unsigned int maxsize, char dma_dir)
+{
+ return NULL;
+}
+
+static inline void comedi_isadma_free(struct comedi_isadma *dma)
+{
+}
+
+#endif /* !IS_ENABLED(CONFIG_ISA_DMA_API) */
+
+#endif /* #ifndef _COMEDI_ISADMA_H */
diff --git a/drivers/staging/comedi/drivers/comedi_parport.c b/drivers/staging/comedi/drivers/comedi_parport.c
new file mode 100644
index 000000000..15a4093ef
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_parport.c
@@ -0,0 +1,314 @@
+/*
+ * comedi_parport.c
+ * Comedi driver for standard parallel port
+ *
+ * For more information see:
+ * http://retired.beyondlogic.org/spp/parallel.htm
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998,2001 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: comedi_parport
+ * Description: Standard PC parallel port
+ * Author: ds
+ * Status: works in immediate mode
+ * Devices: [standard] parallel port (comedi_parport)
+ * Updated: Tue, 30 Apr 2002 21:11:45 -0700
+ *
+ * A cheap and easy way to get a few more digital I/O lines. Steal
+ * additional parallel ports from old computers or your neighbors'
+ * computers.
+ *
+ * Option list:
+ * 0: I/O port base for the parallel port.
+ * 1: IRQ (optional)
+ *
+ * Parallel Port Lines:
+ *
+ * pin subdev chan type name
+ * ----- ------ ---- ---- --------------
+ * 1 2 0 DO strobe
+ * 2 0 0 DIO data 0
+ * 3 0 1 DIO data 1
+ * 4 0 2 DIO data 2
+ * 5 0 3 DIO data 3
+ * 6 0 4 DIO data 4
+ * 7 0 5 DIO data 5
+ * 8 0 6 DIO data 6
+ * 9 0 7 DIO data 7
+ * 10 1 3 DI ack
+ * 11 1 4 DI busy
+ * 12 1 2 DI paper out
+ * 13 1 1 DI select in
+ * 14 2 1 DO auto LF
+ * 15 1 0 DI error
+ * 16 2 2 DO init
+ * 17 2 3 DO select printer
+ * 18-25 ground
+ *
+ * When an IRQ is configured subdevice 3 pretends to be a digital
+ * input subdevice, but it always returns 0 when read. However, if
+ * you run a command with scan_begin_src=TRIG_EXT, it uses pin 10
+ * as a external trigger, which can be used to wake up tasks.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+/*
+ * Register map
+ */
+#define PARPORT_DATA_REG 0x00
+#define PARPORT_STATUS_REG 0x01
+#define PARPORT_CTRL_REG 0x02
+#define PARPORT_CTRL_IRQ_ENA (1 << 4)
+#define PARPORT_CTRL_BIDIR_ENA (1 << 5)
+
+static int parport_data_reg_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + PARPORT_DATA_REG);
+
+ data[1] = inb(dev->iobase + PARPORT_DATA_REG);
+
+ return insn->n;
+}
+
+static int parport_data_reg_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int ctrl;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0xff);
+ if (ret)
+ return ret;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ if (s->io_bits)
+ ctrl &= ~PARPORT_CTRL_BIDIR_ENA;
+ else
+ ctrl |= PARPORT_CTRL_BIDIR_ENA;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+
+ return insn->n;
+}
+
+static int parport_status_reg_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PARPORT_STATUS_REG) >> 3;
+
+ return insn->n;
+}
+
+static int parport_ctrl_reg_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int ctrl;
+
+ if (comedi_dio_update_state(s, data)) {
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ ctrl &= (PARPORT_CTRL_IRQ_ENA | PARPORT_CTRL_BIDIR_ENA);
+ ctrl |= s->state;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int parport_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static int parport_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int parport_intr_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int ctrl;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ ctrl |= PARPORT_CTRL_IRQ_ENA;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+
+ return 0;
+}
+
+static int parport_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int ctrl;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ ctrl &= ~PARPORT_CTRL_IRQ_ENA;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+
+ return 0;
+}
+
+static irqreturn_t parport_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int ctrl;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ if (!(ctrl & PARPORT_CTRL_IRQ_ENA))
+ return IRQ_NONE;
+
+ comedi_buf_write_samples(s, &s->state, 1);
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int parport_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x03);
+ if (ret)
+ return ret;
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], parport_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, dev->irq ? 4 : 3);
+ if (ret)
+ return ret;
+
+ /* Digial I/O subdevice - Parallel port DATA register */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = parport_data_reg_insn_bits;
+ s->insn_config = parport_data_reg_insn_config;
+
+ /* Digial Input subdevice - Parallel port STATUS register */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 5;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = parport_status_reg_insn_bits;
+
+ /* Digial Output subdevice - Parallel port CONTROL register */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = parport_ctrl_reg_insn_bits;
+
+ if (dev->irq) {
+ /* Digial Input subdevice - Interrupt support */
+ s = &dev->subdevices[3];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = parport_intr_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = parport_intr_cmdtest;
+ s->do_cmd = parport_intr_cmd;
+ s->cancel = parport_intr_cancel;
+ }
+
+ outb(0, dev->iobase + PARPORT_DATA_REG);
+ outb(0, dev->iobase + PARPORT_CTRL_REG);
+
+ return 0;
+}
+
+static struct comedi_driver parport_driver = {
+ .driver_name = "comedi_parport",
+ .module = THIS_MODULE,
+ .attach = parport_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(parport_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi: Standard parallel port driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_test.c b/drivers/staging/comedi/drivers/comedi_test.c
new file mode 100644
index 000000000..80d613c0f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_test.c
@@ -0,0 +1,456 @@
+/*
+ comedi/drivers/comedi_test.c
+
+ Generates fake waveform signals that can be read through
+ the command interface. It does _not_ read from any board;
+ it just generates deterministic waveforms.
+ Useful for various testing purposes.
+
+ Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
+ Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: comedi_test
+Description: generates fake waveforms
+Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
+ <fmhess@users.sourceforge.net>, ds
+Devices:
+Status: works
+Updated: Sat, 16 Mar 2002 17:34:48 -0800
+
+This driver is mainly for testing purposes, but can also be used to
+generate sample waveforms on systems that don't have data acquisition
+hardware.
+
+Configuration options:
+ [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
+ [1] - Period in microseconds for fake waveforms (default 0.1 sec)
+
+Generates a sawtooth wave on channel 0, square wave on channel 1, additional
+waveforms could be added to other channels (currently they return flatline
+zero volts).
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <asm/div64.h>
+
+#include <linux/timer.h>
+#include <linux/ktime.h>
+
+#define N_CHANS 8
+
+enum waveform_state_bits {
+ WAVEFORM_AI_RUNNING = 0
+};
+
+/* Data unique to this driver */
+struct waveform_private {
+ struct timer_list timer;
+ ktime_t last; /* time last timer interrupt occurred */
+ unsigned int uvolt_amplitude; /* waveform amplitude in microvolts */
+ unsigned long usec_period; /* waveform period in microseconds */
+ unsigned long usec_current; /* current time (mod waveform period) */
+ unsigned long usec_remainder; /* usec since last scan */
+ unsigned long state_bits;
+ unsigned int scan_period; /* scan period in usec */
+ unsigned int convert_period; /* conversion period in usec */
+ unsigned int ao_loopbacks[N_CHANS];
+};
+
+/* 1000 nanosec in a microsec */
+static const int nano_per_micro = 1000;
+
+/* fake analog input ranges */
+static const struct comedi_lrange waveform_ai_ranges = {
+ 2, {
+ BIP_RANGE(10),
+ BIP_RANGE(5)
+ }
+};
+
+static unsigned short fake_sawtooth(struct comedi_device *dev,
+ unsigned int range_index,
+ unsigned long current_time)
+{
+ struct waveform_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int offset = s->maxdata / 2;
+ u64 value;
+ const struct comedi_krange *krange =
+ &s->range_table->range[range_index];
+ u64 binary_amplitude;
+
+ binary_amplitude = s->maxdata;
+ binary_amplitude *= devpriv->uvolt_amplitude;
+ do_div(binary_amplitude, krange->max - krange->min);
+
+ current_time %= devpriv->usec_period;
+ value = current_time;
+ value *= binary_amplitude * 2;
+ do_div(value, devpriv->usec_period);
+ value -= binary_amplitude; /* get rid of sawtooth's dc offset */
+
+ return offset + value;
+}
+
+static unsigned short fake_squarewave(struct comedi_device *dev,
+ unsigned int range_index,
+ unsigned long current_time)
+{
+ struct waveform_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int offset = s->maxdata / 2;
+ u64 value;
+ const struct comedi_krange *krange =
+ &s->range_table->range[range_index];
+ current_time %= devpriv->usec_period;
+
+ value = s->maxdata;
+ value *= devpriv->uvolt_amplitude;
+ do_div(value, krange->max - krange->min);
+
+ if (current_time < devpriv->usec_period / 2)
+ value *= -1;
+
+ return offset + value;
+}
+
+static unsigned short fake_flatline(struct comedi_device *dev,
+ unsigned int range_index,
+ unsigned long current_time)
+{
+ return dev->read_subdev->maxdata / 2;
+}
+
+/* generates a different waveform depending on what channel is read */
+static unsigned short fake_waveform(struct comedi_device *dev,
+ unsigned int channel, unsigned int range,
+ unsigned long current_time)
+{
+ enum {
+ SAWTOOTH_CHAN,
+ SQUARE_CHAN,
+ };
+ switch (channel) {
+ case SAWTOOTH_CHAN:
+ return fake_sawtooth(dev, range, current_time);
+ case SQUARE_CHAN:
+ return fake_squarewave(dev, range, current_time);
+ default:
+ break;
+ }
+
+ return fake_flatline(dev, range, current_time);
+}
+
+/*
+ This is the background routine used to generate arbitrary data.
+ It should run in the background; therefore it is scheduled by
+ a timer mechanism.
+*/
+static void waveform_ai_interrupt(unsigned long arg)
+{
+ struct comedi_device *dev = (struct comedi_device *)arg;
+ struct waveform_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int i, j;
+ /* all times in microsec */
+ unsigned long elapsed_time;
+ unsigned int num_scans;
+ ktime_t now;
+
+ /* check command is still active */
+ if (!test_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits))
+ return;
+
+ now = ktime_get();
+
+ elapsed_time = ktime_to_us(ktime_sub(now, devpriv->last));
+ devpriv->last = now;
+ num_scans =
+ (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
+ devpriv->usec_remainder =
+ (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
+
+ num_scans = comedi_nscans_left(s, num_scans);
+ for (i = 0; i < num_scans; i++) {
+ for (j = 0; j < cmd->chanlist_len; j++) {
+ unsigned short sample;
+
+ sample = fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
+ CR_RANGE(cmd->chanlist[j]),
+ devpriv->usec_current +
+ i * devpriv->scan_period +
+ j * devpriv->convert_period);
+ comedi_buf_write_samples(s, &sample, 1);
+ }
+ }
+
+ devpriv->usec_current += elapsed_time;
+ devpriv->usec_current %= devpriv->usec_period;
+
+ if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+ else
+ mod_timer(&devpriv->timer, jiffies + 1);
+
+ comedi_handle_events(dev, s);
+}
+
+static int waveform_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_NOW | TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_NOW)
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ nano_per_micro);
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ cmd->convert_arg *
+ cmd->chanlist_len);
+ }
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ /* round to nearest microsec */
+ arg = nano_per_micro *
+ ((arg + (nano_per_micro / 2)) / nano_per_micro);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ /* round to nearest microsec */
+ arg = nano_per_micro *
+ ((arg + (nano_per_micro / 2)) / nano_per_micro);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int waveform_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct waveform_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (cmd->flags & CMDF_PRIORITY) {
+ dev_err(dev->class_dev,
+ "commands at RT priority not supported in this driver\n");
+ return -1;
+ }
+
+ devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
+
+ if (cmd->convert_src == TRIG_NOW)
+ devpriv->convert_period = 0;
+ else /* TRIG_TIMER */
+ devpriv->convert_period = cmd->convert_arg / nano_per_micro;
+
+ devpriv->last = ktime_get();
+ devpriv->usec_current =
+ ((u32)ktime_to_us(devpriv->last)) % devpriv->usec_period;
+ devpriv->usec_remainder = 0;
+
+ devpriv->timer.expires = jiffies + 1;
+ /* mark command as active */
+ smp_mb__before_atomic();
+ set_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
+ smp_mb__after_atomic();
+ add_timer(&devpriv->timer);
+ return 0;
+}
+
+static int waveform_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct waveform_private *devpriv = dev->private;
+
+ /* mark command as no longer active */
+ clear_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
+ smp_mb__after_atomic();
+ /* cannot call del_timer_sync() as may be called from timer routine */
+ del_timer(&devpriv->timer);
+ return 0;
+}
+
+static int waveform_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct waveform_private *devpriv = dev->private;
+ int i, chan = CR_CHAN(insn->chanspec);
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_loopbacks[chan];
+
+ return insn->n;
+}
+
+static int waveform_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct waveform_private *devpriv = dev->private;
+ int i, chan = CR_CHAN(insn->chanspec);
+
+ for (i = 0; i < insn->n; i++)
+ devpriv->ao_loopbacks[chan] = data[i];
+
+ return insn->n;
+}
+
+static int waveform_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct waveform_private *devpriv;
+ struct comedi_subdevice *s;
+ int amplitude = it->options[0];
+ int period = it->options[1];
+ int i;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* set default amplitude and period */
+ if (amplitude <= 0)
+ amplitude = 1000000; /* 1 volt */
+ if (period <= 0)
+ period = 100000; /* 0.1 sec */
+
+ devpriv->uvolt_amplitude = amplitude;
+ devpriv->usec_period = period;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
+ s->n_chan = N_CHANS;
+ s->maxdata = 0xffff;
+ s->range_table = &waveform_ai_ranges;
+ s->len_chanlist = s->n_chan * 2;
+ s->insn_read = waveform_ai_insn_read;
+ s->do_cmd = waveform_ai_cmd;
+ s->do_cmdtest = waveform_ai_cmdtest;
+ s->cancel = waveform_ai_cancel;
+
+ s = &dev->subdevices[1];
+ dev->write_subdev = s;
+ /* analog output subdevice (loopback) */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = N_CHANS;
+ s->maxdata = 0xffff;
+ s->range_table = &waveform_ai_ranges;
+ s->insn_write = waveform_ao_insn_write;
+
+ /* Our default loopback value is just a 0V flatline */
+ for (i = 0; i < s->n_chan; i++)
+ devpriv->ao_loopbacks[i] = s->maxdata / 2;
+
+ setup_timer(&devpriv->timer, waveform_ai_interrupt,
+ (unsigned long)dev);
+
+ dev_info(dev->class_dev,
+ "%s: %i microvolt, %li microsecond waveform attached\n",
+ dev->board_name,
+ devpriv->uvolt_amplitude, devpriv->usec_period);
+
+ return 0;
+}
+
+static void waveform_detach(struct comedi_device *dev)
+{
+ struct waveform_private *devpriv = dev->private;
+
+ if (devpriv)
+ del_timer_sync(&devpriv->timer);
+}
+
+static struct comedi_driver waveform_driver = {
+ .driver_name = "comedi_test",
+ .module = THIS_MODULE,
+ .attach = waveform_attach,
+ .detach = waveform_detach,
+};
+module_comedi_driver(waveform_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/contec_pci_dio.c b/drivers/staging/comedi/drivers/contec_pci_dio.c
new file mode 100644
index 000000000..4956a49a6
--- /dev/null
+++ b/drivers/staging/comedi/drivers/contec_pci_dio.c
@@ -0,0 +1,125 @@
+/*
+ comedi/drivers/contec_pci_dio.c
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: contec_pci_dio
+Description: Contec PIO1616L digital I/O board
+Devices: [Contec] PIO1616L (contec_pci_dio)
+Author: Stefano Rivoir <s.rivoir@gts.it>
+Updated: Wed, 27 Jun 2007 13:00:06 +0100
+Status: works
+
+Configuration Options: not applicable, uses comedi PCI auto config
+*/
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * Register map
+ */
+#define PIO1616L_DI_REG 0x00
+#define PIO1616L_DO_REG 0x02
+
+static int contec_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PIO1616L_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int contec_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = inw(dev->iobase + PIO1616L_DI_REG);
+
+ return insn->n;
+}
+
+static int contec_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = contec_di_insn_bits;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = contec_do_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver contec_pci_dio_driver = {
+ .driver_name = "contec_pci_dio",
+ .module = THIS_MODULE,
+ .auto_attach = contec_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int contec_pci_dio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &contec_pci_dio_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id contec_pci_dio_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CONTEC, 0x8172) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, contec_pci_dio_pci_table);
+
+static struct pci_driver contec_pci_dio_pci_driver = {
+ .name = "contec_pci_dio",
+ .id_table = contec_pci_dio_pci_table,
+ .probe = contec_pci_dio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(contec_pci_dio_driver, contec_pci_dio_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dac02.c b/drivers/staging/comedi/drivers/dac02.c
new file mode 100644
index 000000000..a6798ad8f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dac02.c
@@ -0,0 +1,150 @@
+/*
+ * dac02.c
+ * Comedi driver for DAC02 compatible boards
+ * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on the poc driver
+ * Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2001 David A. Schleef <ds@schleef.org>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: dac02
+ * Description: Comedi driver for DAC02 compatible boards
+ * Devices: [Keithley Metrabyte] DAC-02 (dac02)
+ * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Updated: Tue, 11 Mar 2014 11:27:19 -0700
+ * Status: unknown
+ *
+ * Configuration options:
+ * [0] - I/O port base
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+/*
+ * The output range is selected by jumpering pins on the I/O connector.
+ *
+ * Range Chan # Jumper pins Output
+ * ------------- ------ ------------- -----------------
+ * 0 to 5V 0 21 to 22 24
+ * 1 15 to 16 18
+ * 0 to 10V 0 20 to 22 24
+ * 1 14 to 16 18
+ * +/-5V 0 21 to 22 23
+ * 1 15 to 16 17
+ * +/-10V 0 20 to 22 23
+ * 1 14 to 16 17
+ * 4 to 20mA 0 21 to 22 25
+ * 1 15 to 16 19
+ * AC reference 0 In on pin 22 24 (2-quadrant)
+ * In on pin 22 23 (4-quadrant)
+ * 1 In on pin 16 18 (2-quadrant)
+ * In on pin 16 17 (4-quadrant)
+ */
+static const struct comedi_lrange das02_ao_ranges = {
+ 6, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ RANGE_mA(4, 20),
+ RANGE_ext(0, 1)
+ }
+};
+
+/*
+ * Register I/O map
+ */
+#define DAC02_AO_LSB(x) (0x00 + ((x) * 2))
+#define DAC02_AO_MSB(x) (0x01 + ((x) * 2))
+
+static int dac02_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ s->readback[chan] = val;
+
+ /*
+ * Unipolar outputs are true binary encoding.
+ * Bipolar outputs are complementary offset binary
+ * (that is, 0 = +full scale, maxdata = -full scale).
+ */
+ if (comedi_range_is_bipolar(s, range))
+ val = s->maxdata - val;
+
+ /*
+ * DACs are double-buffered.
+ * Write LSB then MSB to latch output.
+ */
+ outb((val << 4) & 0xf0, dev->iobase + DAC02_AO_LSB(chan));
+ outb((val >> 4) & 0xff, dev->iobase + DAC02_AO_MSB(chan));
+ }
+
+ return insn->n;
+}
+
+static int dac02_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x08);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &das02_ao_ranges;
+ s->insn_write = dac02_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver dac02_driver = {
+ .driver_name = "dac02",
+ .module = THIS_MODULE,
+ .attach = dac02_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dac02_driver);
+
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("Comedi driver for DAC02 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/daqboard2000.c b/drivers/staging/comedi/drivers/daqboard2000.c
new file mode 100644
index 000000000..0bba7a9c0
--- /dev/null
+++ b/drivers/staging/comedi/drivers/daqboard2000.c
@@ -0,0 +1,764 @@
+/*
+ comedi/drivers/daqboard2000.c
+ hardware driver for IOtech DAQboard/2000
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+/*
+Driver: daqboard2000
+Description: IOTech DAQBoard/2000
+Author: Anders Blomdell <anders.blomdell@control.lth.se>
+Status: works
+Updated: Mon, 14 Apr 2008 15:28:52 +0100
+Devices: [IOTech] DAQBoard/2000 (daqboard2000)
+
+Much of the functionality of this driver was determined from reading
+the source code for the Windows driver.
+
+The FPGA on the board requires fimware, which is available from
+http://www.comedi.org in the comedi_nonfree_firmware tarball.
+
+Configuration options: not applicable, uses PCI auto config
+*/
+/*
+ This card was obviously never intended to leave the Windows world,
+ since it lacked all kind of hardware documentation (except for cable
+ pinouts, plug and pray has something to catch up with yet).
+
+ With some help from our swedish distributor, we got the Windows sourcecode
+ for the card, and here are the findings so far.
+
+ 1. A good document that describes the PCI interface chip is 9080db-106.pdf
+ available from http://www.plxtech.com/products/io/pci9080
+
+ 2. The initialization done so far is:
+ a. program the FPGA (windows code sans a lot of error messages)
+ b.
+
+ 3. Analog out seems to work OK with DAC's disabled, if DAC's are enabled,
+ you have to output values to all enabled DAC's until result appears, I
+ guess that it has something to do with pacer clocks, but the source
+ gives me no clues. I'll keep it simple so far.
+
+ 4. Analog in.
+ Each channel in the scanlist seems to be controlled by four
+ control words:
+
+ Word0:
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ! | | | ! | | | ! | | | ! | | | !
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Word1:
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ! | | | ! | | | ! | | | ! | | | !
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | | | | | |
+ +------+------+ | | | | +-- Digital input (??)
+ | | | | +---- 10 us settling time
+ | | | +------ Suspend acquisition (last to scan)
+ | | +-------- Simultaneous sample and hold
+ | +---------- Signed data format
+ +------------------------- Correction offset low
+
+ Word2:
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ! | | | ! | | | ! | | | ! | | | !
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | | | | | | | | |
+ +-----+ +--+--+ +++ +++ +--+--+
+ | | | | +----- Expansion channel
+ | | | +----------- Expansion gain
+ | | +--------------- Channel (low)
+ | +--------------------- Correction offset high
+ +----------------------------- Correction gain low
+ Word3:
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ! | | | ! | | | ! | | | ! | | | !
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | | | | | | | |
+ +------+------+ | | +-+-+ | | +-- Low bank enable
+ | | | | | +---- High bank enable
+ | | | | +------ Hi/low select
+ | | | +---------- Gain (1,?,2,4,8,16,32,64)
+ | | +-------------- differential/single ended
+ | +---------------- Unipolar
+ +------------------------- Correction gain high
+
+ 999. The card seems to have an incredible amount of capabilities, but
+ trying to reverse engineer them from the Windows source is beyond my
+ patience.
+
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "8255.h"
+
+#define DAQBOARD2000_FIRMWARE "/*(DEBLOBBED)*/"
+
+#define DAQBOARD2000_SUBSYSTEM_IDS2 0x0002 /* Daqboard/2000 - 2 Dacs */
+#define DAQBOARD2000_SUBSYSTEM_IDS4 0x0004 /* Daqboard/2000 - 4 Dacs */
+
+/* Initialization bits for the Serial EEPROM Control Register */
+#define DAQBOARD2000_SECRProgPinHi 0x8001767e
+#define DAQBOARD2000_SECRProgPinLo 0x8000767e
+#define DAQBOARD2000_SECRLocalBusHi 0xc000767e
+#define DAQBOARD2000_SECRLocalBusLo 0x8000767e
+#define DAQBOARD2000_SECRReloadHi 0xa000767e
+#define DAQBOARD2000_SECRReloadLo 0x8000767e
+
+/* SECR status bits */
+#define DAQBOARD2000_EEPROM_PRESENT 0x10000000
+
+/* CPLD status bits */
+#define DAQBOARD2000_CPLD_INIT 0x0002
+#define DAQBOARD2000_CPLD_DONE 0x0004
+
+static const struct comedi_lrange range_daqboard2000_ai = {
+ 13, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(0.3125),
+ BIP_RANGE(0.156),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ UNI_RANGE(0.625),
+ UNI_RANGE(0.3125)
+ }
+};
+
+/*
+ * Register Memory Map
+ */
+#define acqControl 0x00 /* u16 */
+#define acqScanListFIFO 0x02 /* u16 */
+#define acqPacerClockDivLow 0x04 /* u32 */
+#define acqScanCounter 0x08 /* u16 */
+#define acqPacerClockDivHigh 0x0a /* u16 */
+#define acqTriggerCount 0x0c /* u16 */
+#define acqResultsFIFO 0x10 /* u16 */
+#define acqResultsShadow 0x14 /* u16 */
+#define acqAdcResult 0x18 /* u16 */
+#define dacScanCounter 0x1c /* u16 */
+#define dacControl 0x20 /* u16 */
+#define dacFIFO 0x24 /* s16 */
+#define dacPacerClockDiv 0x2a /* u16 */
+#define refDacs 0x2c /* u16 */
+#define dioControl 0x30 /* u16 */
+#define dioP3hsioData 0x32 /* s16 */
+#define dioP3Control 0x34 /* u16 */
+#define calEepromControl 0x36 /* u16 */
+#define dacSetting(x) (0x38 + (x)*2) /* s16 */
+#define dioP2ExpansionIO8Bit 0x40 /* s16 */
+#define ctrTmrControl 0x80 /* u16 */
+#define ctrInput(x) (0x88 + (x)*2) /* s16 */
+#define timerDivisor(x) (0xa0 + (x)*2) /* u16 */
+#define dmaControl 0xb0 /* u16 */
+#define trigControl 0xb2 /* u16 */
+#define calEeprom 0xb8 /* u16 */
+#define acqDigitalMark 0xba /* u16 */
+#define trigDacs 0xbc /* u16 */
+#define dioP2ExpansionIO16Bit(x) (0xc0 + (x)*2) /* s16 */
+
+/* Scan Sequencer programming */
+#define DAQBOARD2000_SeqStartScanList 0x0011
+#define DAQBOARD2000_SeqStopScanList 0x0010
+
+/* Prepare for acquisition */
+#define DAQBOARD2000_AcqResetScanListFifo 0x0004
+#define DAQBOARD2000_AcqResetResultsFifo 0x0002
+#define DAQBOARD2000_AcqResetConfigPipe 0x0001
+
+/* Acqusition status bits */
+#define DAQBOARD2000_AcqResultsFIFOMore1Sample 0x0001
+#define DAQBOARD2000_AcqResultsFIFOHasValidData 0x0002
+#define DAQBOARD2000_AcqResultsFIFOOverrun 0x0004
+#define DAQBOARD2000_AcqLogicScanning 0x0008
+#define DAQBOARD2000_AcqConfigPipeFull 0x0010
+#define DAQBOARD2000_AcqScanListFIFOEmpty 0x0020
+#define DAQBOARD2000_AcqAdcNotReady 0x0040
+#define DAQBOARD2000_ArbitrationFailure 0x0080
+#define DAQBOARD2000_AcqPacerOverrun 0x0100
+#define DAQBOARD2000_DacPacerOverrun 0x0200
+#define DAQBOARD2000_AcqHardwareError 0x01c0
+
+/* Scan Sequencer programming */
+#define DAQBOARD2000_SeqStartScanList 0x0011
+#define DAQBOARD2000_SeqStopScanList 0x0010
+
+/* Pacer Clock Control */
+#define DAQBOARD2000_AdcPacerInternal 0x0030
+#define DAQBOARD2000_AdcPacerExternal 0x0032
+#define DAQBOARD2000_AdcPacerEnable 0x0031
+#define DAQBOARD2000_AdcPacerEnableDacPacer 0x0034
+#define DAQBOARD2000_AdcPacerDisable 0x0030
+#define DAQBOARD2000_AdcPacerNormalMode 0x0060
+#define DAQBOARD2000_AdcPacerCompatibilityMode 0x0061
+#define DAQBOARD2000_AdcPacerInternalOutEnable 0x0008
+#define DAQBOARD2000_AdcPacerExternalRising 0x0100
+
+/* DAC status */
+#define DAQBOARD2000_DacFull 0x0001
+#define DAQBOARD2000_RefBusy 0x0002
+#define DAQBOARD2000_TrgBusy 0x0004
+#define DAQBOARD2000_CalBusy 0x0008
+#define DAQBOARD2000_Dac0Busy 0x0010
+#define DAQBOARD2000_Dac1Busy 0x0020
+#define DAQBOARD2000_Dac2Busy 0x0040
+#define DAQBOARD2000_Dac3Busy 0x0080
+
+/* DAC control */
+#define DAQBOARD2000_Dac0Enable 0x0021
+#define DAQBOARD2000_Dac1Enable 0x0031
+#define DAQBOARD2000_Dac2Enable 0x0041
+#define DAQBOARD2000_Dac3Enable 0x0051
+#define DAQBOARD2000_DacEnableBit 0x0001
+#define DAQBOARD2000_Dac0Disable 0x0020
+#define DAQBOARD2000_Dac1Disable 0x0030
+#define DAQBOARD2000_Dac2Disable 0x0040
+#define DAQBOARD2000_Dac3Disable 0x0050
+#define DAQBOARD2000_DacResetFifo 0x0004
+#define DAQBOARD2000_DacPatternDisable 0x0060
+#define DAQBOARD2000_DacPatternEnable 0x0061
+#define DAQBOARD2000_DacSelectSignedData 0x0002
+#define DAQBOARD2000_DacSelectUnsignedData 0x0000
+
+/* Trigger Control */
+#define DAQBOARD2000_TrigAnalog 0x0000
+#define DAQBOARD2000_TrigTTL 0x0010
+#define DAQBOARD2000_TrigTransHiLo 0x0004
+#define DAQBOARD2000_TrigTransLoHi 0x0000
+#define DAQBOARD2000_TrigAbove 0x0000
+#define DAQBOARD2000_TrigBelow 0x0004
+#define DAQBOARD2000_TrigLevelSense 0x0002
+#define DAQBOARD2000_TrigEdgeSense 0x0000
+#define DAQBOARD2000_TrigEnable 0x0001
+#define DAQBOARD2000_TrigDisable 0x0000
+
+/* Reference Dac Selection */
+#define DAQBOARD2000_PosRefDacSelect 0x0100
+#define DAQBOARD2000_NegRefDacSelect 0x0000
+
+struct daq200_boardtype {
+ const char *name;
+ int id;
+};
+static const struct daq200_boardtype boardtypes[] = {
+ {"ids2", DAQBOARD2000_SUBSYSTEM_IDS2},
+ {"ids4", DAQBOARD2000_SUBSYSTEM_IDS4},
+};
+
+struct daqboard2000_private {
+ enum {
+ card_daqboard_2000
+ } card;
+ void __iomem *plx;
+};
+
+static void writeAcqScanListEntry(struct comedi_device *dev, u16 entry)
+{
+ /* udelay(4); */
+ writew(entry & 0x00ff, dev->mmio + acqScanListFIFO);
+ /* udelay(4); */
+ writew((entry >> 8) & 0x00ff, dev->mmio + acqScanListFIFO);
+}
+
+static void setup_sampling(struct comedi_device *dev, int chan, int gain)
+{
+ u16 word0, word1, word2, word3;
+
+ /* Channel 0-7 diff, channel 8-23 single ended */
+ word0 = 0;
+ word1 = 0x0004; /* Last scan */
+ word2 = (chan << 6) & 0x00c0;
+ switch (chan / 4) {
+ case 0:
+ word3 = 0x0001;
+ break;
+ case 1:
+ word3 = 0x0002;
+ break;
+ case 2:
+ word3 = 0x0005;
+ break;
+ case 3:
+ word3 = 0x0006;
+ break;
+ case 4:
+ word3 = 0x0041;
+ break;
+ case 5:
+ word3 = 0x0042;
+ break;
+ default:
+ word3 = 0;
+ break;
+ }
+/*
+ dev->eeprom.correctionDACSE[i][j][k].offset = 0x800;
+ dev->eeprom.correctionDACSE[i][j][k].gain = 0xc00;
+*/
+ /* These should be read from EEPROM */
+ word2 |= 0x0800;
+ word3 |= 0xc000;
+ writeAcqScanListEntry(dev, word0);
+ writeAcqScanListEntry(dev, word1);
+ writeAcqScanListEntry(dev, word2);
+ writeAcqScanListEntry(dev, word3);
+}
+
+static int daqboard2000_ai_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readw(dev->mmio + acqControl);
+ if (status & context)
+ return 0;
+ return -EBUSY;
+}
+
+static int daqboard2000_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int gain, chan;
+ int ret;
+ int i;
+
+ writew(DAQBOARD2000_AcqResetScanListFifo |
+ DAQBOARD2000_AcqResetResultsFifo |
+ DAQBOARD2000_AcqResetConfigPipe, dev->mmio + acqControl);
+
+ /*
+ * If pacer clock is not set to some high value (> 10 us), we
+ * risk multiple samples to be put into the result FIFO.
+ */
+ /* 1 second, should be long enough */
+ writel(1000000, dev->mmio + acqPacerClockDivLow);
+ writew(0, dev->mmio + acqPacerClockDivHigh);
+
+ gain = CR_RANGE(insn->chanspec);
+ chan = CR_CHAN(insn->chanspec);
+
+ /* This doesn't look efficient. I decided to take the conservative
+ * approach when I did the insn conversion. Perhaps it would be
+ * better to have broken it completely, then someone would have been
+ * forced to fix it. --ds */
+ for (i = 0; i < insn->n; i++) {
+ setup_sampling(dev, chan, gain);
+ /* Enable reading from the scanlist FIFO */
+ writew(DAQBOARD2000_SeqStartScanList, dev->mmio + acqControl);
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
+ DAQBOARD2000_AcqConfigPipeFull);
+ if (ret)
+ return ret;
+
+ writew(DAQBOARD2000_AdcPacerEnable, dev->mmio + acqControl);
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
+ DAQBOARD2000_AcqLogicScanning);
+ if (ret)
+ return ret;
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
+ DAQBOARD2000_AcqResultsFIFOHasValidData);
+ if (ret)
+ return ret;
+
+ data[i] = readw(dev->mmio + acqResultsFIFO);
+ writew(DAQBOARD2000_AdcPacerDisable, dev->mmio + acqControl);
+ writew(DAQBOARD2000_SeqStopScanList, dev->mmio + acqControl);
+ }
+
+ return i;
+}
+
+static int daqboard2000_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int status;
+
+ status = readw(dev->mmio + dacControl);
+ if ((status & ((chan + 1) * 0x0010)) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int daqboard2000_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+ int ret;
+
+ writew(val, dev->mmio + dacSetting(chan));
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static void daqboard2000_resetLocalBus(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ writel(DAQBOARD2000_SECRLocalBusHi, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRLocalBusLo, devpriv->plx + 0x6c);
+ mdelay(10);
+}
+
+static void daqboard2000_reloadPLX(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRReloadHi, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c);
+ mdelay(10);
+}
+
+static void daqboard2000_pulseProgPin(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ writel(DAQBOARD2000_SECRProgPinHi, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRProgPinLo, devpriv->plx + 0x6c);
+ mdelay(10); /* Not in the original code, but I like symmetry... */
+}
+
+static int daqboard2000_pollCPLD(struct comedi_device *dev, int mask)
+{
+ int result = 0;
+ int i;
+ int cpld;
+
+ /* timeout after 50 tries -> 5ms */
+ for (i = 0; i < 50; i++) {
+ cpld = readw(dev->mmio + 0x1000);
+ if ((cpld & mask) == mask) {
+ result = 1;
+ break;
+ }
+ udelay(100);
+ }
+ udelay(5);
+ return result;
+}
+
+static int daqboard2000_writeCPLD(struct comedi_device *dev, int data)
+{
+ int result = 0;
+
+ udelay(10);
+ writew(data, dev->mmio + 0x1000);
+ if ((readw(dev->mmio + 0x1000) & DAQBOARD2000_CPLD_INIT) ==
+ DAQBOARD2000_CPLD_INIT) {
+ result = 1;
+ }
+ return result;
+}
+
+static int initialize_daqboard2000(struct comedi_device *dev,
+ const u8 *cpld_array, size_t len,
+ unsigned long context)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ int result = -EIO;
+ /* Read the serial EEPROM control register */
+ int secr;
+ int retry;
+ size_t i;
+
+ /* Check to make sure the serial eeprom is present on the board */
+ secr = readl(devpriv->plx + 0x6c);
+ if (!(secr & DAQBOARD2000_EEPROM_PRESENT))
+ return -EIO;
+
+ for (retry = 0; retry < 3; retry++) {
+ daqboard2000_resetLocalBus(dev);
+ daqboard2000_reloadPLX(dev);
+ daqboard2000_pulseProgPin(dev);
+ if (daqboard2000_pollCPLD(dev, DAQBOARD2000_CPLD_INIT)) {
+ for (i = 0; i < len; i++) {
+ if (cpld_array[i] == 0xff &&
+ cpld_array[i + 1] == 0x20)
+ break;
+ }
+ for (; i < len; i += 2) {
+ int data =
+ (cpld_array[i] << 8) + cpld_array[i + 1];
+ if (!daqboard2000_writeCPLD(dev, data))
+ break;
+ }
+ if (i >= len) {
+ daqboard2000_resetLocalBus(dev);
+ daqboard2000_reloadPLX(dev);
+ result = 0;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+static void daqboard2000_adcStopDmaTransfer(struct comedi_device *dev)
+{
+}
+
+static void daqboard2000_adcDisarm(struct comedi_device *dev)
+{
+ /* Disable hardware triggers */
+ udelay(2);
+ writew(DAQBOARD2000_TrigAnalog | DAQBOARD2000_TrigDisable,
+ dev->mmio + trigControl);
+ udelay(2);
+ writew(DAQBOARD2000_TrigTTL | DAQBOARD2000_TrigDisable,
+ dev->mmio + trigControl);
+
+ /* Stop the scan list FIFO from loading the configuration pipe */
+ udelay(2);
+ writew(DAQBOARD2000_SeqStopScanList, dev->mmio + acqControl);
+
+ /* Stop the pacer clock */
+ udelay(2);
+ writew(DAQBOARD2000_AdcPacerDisable, dev->mmio + acqControl);
+
+ /* Stop the input dma (abort channel 1) */
+ daqboard2000_adcStopDmaTransfer(dev);
+}
+
+static void daqboard2000_activateReferenceDacs(struct comedi_device *dev)
+{
+ unsigned int val;
+ int timeout;
+
+ /* Set the + reference dac value in the FPGA */
+ writew(0x80 | DAQBOARD2000_PosRefDacSelect, dev->mmio + refDacs);
+ for (timeout = 0; timeout < 20; timeout++) {
+ val = readw(dev->mmio + dacControl);
+ if ((val & DAQBOARD2000_RefBusy) == 0)
+ break;
+ udelay(2);
+ }
+
+ /* Set the - reference dac value in the FPGA */
+ writew(0x80 | DAQBOARD2000_NegRefDacSelect, dev->mmio + refDacs);
+ for (timeout = 0; timeout < 20; timeout++) {
+ val = readw(dev->mmio + dacControl);
+ if ((val & DAQBOARD2000_RefBusy) == 0)
+ break;
+ udelay(2);
+ }
+}
+
+static void daqboard2000_initializeCtrs(struct comedi_device *dev)
+{
+}
+
+static void daqboard2000_initializeTmrs(struct comedi_device *dev)
+{
+}
+
+static void daqboard2000_dacDisarm(struct comedi_device *dev)
+{
+}
+
+static void daqboard2000_initializeAdc(struct comedi_device *dev)
+{
+ daqboard2000_adcDisarm(dev);
+ daqboard2000_activateReferenceDacs(dev);
+ daqboard2000_initializeCtrs(dev);
+ daqboard2000_initializeTmrs(dev);
+}
+
+static void daqboard2000_initializeDac(struct comedi_device *dev)
+{
+ daqboard2000_dacDisarm(dev);
+}
+
+static int daqboard2000_8255_cb(struct comedi_device *dev,
+ int dir, int port, int data,
+ unsigned long iobase)
+{
+ if (dir) {
+ writew(data, dev->mmio + iobase + port * 2);
+ return 0;
+ }
+ return readw(dev->mmio + iobase + port * 2);
+}
+
+static const void *daqboard2000_find_boardinfo(struct comedi_device *dev,
+ struct pci_dev *pcidev)
+{
+ const struct daq200_boardtype *board;
+ int i;
+
+ if (pcidev->subsystem_device != PCI_VENDOR_ID_IOTECH)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(boardtypes); i++) {
+ board = &boardtypes[i];
+ if (pcidev->subsystem_device == board->id)
+ return board;
+ }
+ return NULL;
+}
+
+static int daqboard2000_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct daq200_boardtype *board;
+ struct daqboard2000_private *devpriv;
+ struct comedi_subdevice *s;
+ int result;
+
+ board = daqboard2000_find_boardinfo(dev, pcidev);
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ result = comedi_pci_enable(dev);
+ if (result)
+ return result;
+
+ devpriv->plx = pci_ioremap_bar(pcidev, 0);
+ dev->mmio = pci_ioremap_bar(pcidev, 2);
+ if (!devpriv->plx || !dev->mmio)
+ return -ENOMEM;
+
+ result = comedi_alloc_subdevices(dev, 3);
+ if (result)
+ return result;
+
+ readl(devpriv->plx + 0x6c);
+
+ result = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ DAQBOARD2000_FIRMWARE,
+ initialize_daqboard2000, 0);
+ if (result < 0)
+ return result;
+
+ daqboard2000_initializeAdc(dev);
+ daqboard2000_initializeDac(dev);
+
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 24;
+ s->maxdata = 0xffff;
+ s->insn_read = daqboard2000_ai_insn_read;
+ s->range_table = &range_daqboard2000_ai;
+
+ s = &dev->subdevices[1];
+ /* ao subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0xffff;
+ s->insn_write = daqboard2000_ao_insn_write;
+ s->range_table = &range_bipolar10;
+
+ result = comedi_alloc_subdev_readback(s);
+ if (result)
+ return result;
+
+ s = &dev->subdevices[2];
+ result = subdev_8255_init(dev, s, daqboard2000_8255_cb,
+ dioP2ExpansionIO8Bit);
+ if (result)
+ return result;
+
+ return 0;
+}
+
+static void daqboard2000_detach(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ if (devpriv && devpriv->plx)
+ iounmap(devpriv->plx);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver daqboard2000_driver = {
+ .driver_name = "daqboard2000",
+ .module = THIS_MODULE,
+ .auto_attach = daqboard2000_auto_attach,
+ .detach = daqboard2000_detach,
+};
+
+static int daqboard2000_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &daqboard2000_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id daqboard2000_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_IOTECH, 0x0409) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, daqboard2000_pci_table);
+
+static struct pci_driver daqboard2000_pci_driver = {
+ .name = "daqboard2000",
+ .id_table = daqboard2000_pci_table,
+ .probe = daqboard2000_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(daqboard2000_driver, daqboard2000_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
+/*(DEBLOBBED)*/
diff --git a/drivers/staging/comedi/drivers/das08.c b/drivers/staging/comedi/drivers/das08.c
new file mode 100644
index 000000000..73f4c8dbb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08.c
@@ -0,0 +1,489 @@
+/*
+ * comedi/drivers/das08.c
+ * comedi module for common DAS08 support (used by ISA/PCI/PCMCIA drivers)
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+#include "8255.h"
+#include "comedi_8254.h"
+#include "das08.h"
+
+/*
+ cio-das08.pdf
+
+ "isa-das08"
+
+ 0 a/d bits 0-3 start 8 bit
+ 1 a/d bits 4-11 start 12 bit
+ 2 eoc, ip1-3, irq, mux op1-4, inte, mux
+ 3 unused unused
+ 4567 8254
+ 89ab 8255
+
+ requires hard-wiring for async ai
+
+*/
+
+#define DAS08_LSB 0
+#define DAS08_MSB 1
+#define DAS08_TRIG_12BIT 1
+#define DAS08_STATUS 2
+#define DAS08_EOC (1<<7)
+#define DAS08_IRQ (1<<3)
+#define DAS08_IP(x) (((x)>>4)&0x7)
+#define DAS08_CONTROL 2
+#define DAS08_MUX_MASK 0x7
+#define DAS08_MUX(x) ((x) & DAS08_MUX_MASK)
+#define DAS08_INTE (1<<3)
+#define DAS08_DO_MASK 0xf0
+#define DAS08_OP(x) (((x) << 4) & DAS08_DO_MASK)
+
+/*
+ cio-das08jr.pdf
+
+ "das08/jr-ao"
+
+ 0 a/d bits 0-3 unused
+ 1 a/d bits 4-11 start 12 bit
+ 2 eoc, mux mux
+ 3 di do
+ 4 unused ao0_lsb
+ 5 unused ao0_msb
+ 6 unused ao1_lsb
+ 7 unused ao1_msb
+
+*/
+
+#define DAS08JR_DIO 3
+#define DAS08JR_AO_LSB(x) ((x) ? 6 : 4)
+#define DAS08JR_AO_MSB(x) ((x) ? 7 : 5)
+
+/*
+ cio-das08_aox.pdf
+
+ "das08-aoh"
+ "das08-aol"
+ "das08-aom"
+
+ 0 a/d bits 0-3 start 8 bit
+ 1 a/d bits 4-11 start 12 bit
+ 2 eoc, ip1-3, irq, mux op1-4, inte, mux
+ 3 mux, gain status gain control
+ 4567 8254
+ 8 unused ao0_lsb
+ 9 unused ao0_msb
+ a unused ao1_lsb
+ b unused ao1_msb
+ 89ab
+ cdef 8255
+*/
+
+#define DAS08AO_GAIN_CONTROL 3
+#define DAS08AO_GAIN_STATUS 3
+
+#define DAS08AO_AO_LSB(x) ((x) ? 0xa : 8)
+#define DAS08AO_AO_MSB(x) ((x) ? 0xb : 9)
+#define DAS08AO_AO_UPDATE 8
+
+/* gainlist same as _pgx_ below */
+
+static const struct comedi_lrange range_das08_pgl = {
+ 9, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_das08_pgh = {
+ 12, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.01),
+ BIP_RANGE(0.005),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_das08_pgm = {
+ 9, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.01),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01)
+ }
+}; /*
+ cio-das08jr.pdf
+
+ "das08/jr-ao"
+
+ 0 a/d bits 0-3 unused
+ 1 a/d bits 4-11 start 12 bit
+ 2 eoc, mux mux
+ 3 di do
+ 4 unused ao0_lsb
+ 5 unused ao0_msb
+ 6 unused ao1_lsb
+ 7 unused ao1_msb
+
+ */
+
+static const struct comedi_lrange *const das08_ai_lranges[] = {
+ &range_unknown,
+ &range_bipolar5,
+ &range_das08_pgh,
+ &range_das08_pgl,
+ &range_das08_pgm,
+};
+
+static const int das08_pgh_gainlist[] = {
+ 8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7
+};
+static const int das08_pgl_gainlist[] = { 8, 0, 2, 4, 6, 1, 3, 5, 7 };
+static const int das08_pgm_gainlist[] = { 8, 0, 10, 12, 14, 9, 11, 13, 15 };
+
+static const int *const das08_gainlists[] = {
+ NULL,
+ NULL,
+ das08_pgh_gainlist,
+ das08_pgl_gainlist,
+ das08_pgm_gainlist,
+};
+
+static int das08_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS08_STATUS);
+ if ((status & DAS08_EOC) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct das08_board_struct *thisboard = dev->board_ptr;
+ struct das08_private_struct *devpriv = dev->private;
+ int n;
+ int chan;
+ int range;
+ int lsb, msb;
+ int ret;
+
+ chan = CR_CHAN(insn->chanspec);
+ range = CR_RANGE(insn->chanspec);
+
+ /* clear crap */
+ inb(dev->iobase + DAS08_LSB);
+ inb(dev->iobase + DAS08_MSB);
+
+ /* set multiplexer */
+ /* lock to prevent race with digital output */
+ spin_lock(&dev->spinlock);
+ devpriv->do_mux_bits &= ~DAS08_MUX_MASK;
+ devpriv->do_mux_bits |= DAS08_MUX(chan);
+ outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL);
+ spin_unlock(&dev->spinlock);
+
+ if (s->range_table->length > 1) {
+ /* set gain/range */
+ range = CR_RANGE(insn->chanspec);
+ outb(devpriv->pg_gainlist[range],
+ dev->iobase + DAS08AO_GAIN_CONTROL);
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ /* clear over-range bits for 16-bit boards */
+ if (thisboard->ai_nbits == 16)
+ if (inb(dev->iobase + DAS08_MSB) & 0x80)
+ dev_info(dev->class_dev, "over-range\n");
+
+ /* trigger conversion */
+ outb_p(0, dev->iobase + DAS08_TRIG_12BIT);
+
+ ret = comedi_timeout(dev, s, insn, das08_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ msb = inb(dev->iobase + DAS08_MSB);
+ lsb = inb(dev->iobase + DAS08_LSB);
+ if (thisboard->ai_encoding == das08_encode12) {
+ data[n] = (lsb >> 4) | (msb << 4);
+ } else if (thisboard->ai_encoding == das08_pcm_encode12) {
+ data[n] = (msb << 8) + lsb;
+ } else if (thisboard->ai_encoding == das08_encode16) {
+ /* FPOS 16-bit boards are sign-magnitude */
+ if (msb & 0x80)
+ data[n] = (1 << 15) | lsb | ((msb & 0x7f) << 8);
+ else
+ data[n] = (1 << 15) - (lsb | (msb & 0x7f) << 8);
+ } else {
+ dev_err(dev->class_dev, "bug! unknown ai encoding\n");
+ return -1;
+ }
+ }
+
+ return n;
+}
+
+static int das08_di_rbits(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[0] = 0;
+ data[1] = DAS08_IP(inb(dev->iobase + DAS08_STATUS));
+
+ return insn->n;
+}
+
+static int das08_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das08_private_struct *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data)) {
+ /* prevent race with setting of analog input mux */
+ spin_lock(&dev->spinlock);
+ devpriv->do_mux_bits &= ~DAS08_DO_MASK;
+ devpriv->do_mux_bits |= DAS08_OP(s->state);
+ outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL);
+ spin_unlock(&dev->spinlock);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int das08jr_di_rbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[0] = 0;
+ data[1] = inb(dev->iobase + DAS08JR_DIO);
+
+ return insn->n;
+}
+
+static int das08jr_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS08JR_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void das08_ao_set_data(struct comedi_device *dev,
+ unsigned int chan, unsigned int data)
+{
+ const struct das08_board_struct *thisboard = dev->board_ptr;
+ unsigned char lsb;
+ unsigned char msb;
+
+ lsb = data & 0xff;
+ msb = (data >> 8) & 0xff;
+ if (thisboard->is_jr) {
+ outb(lsb, dev->iobase + DAS08JR_AO_LSB(chan));
+ outb(msb, dev->iobase + DAS08JR_AO_MSB(chan));
+ /* load DACs */
+ inb(dev->iobase + DAS08JR_DIO);
+ } else {
+ outb(lsb, dev->iobase + DAS08AO_AO_LSB(chan));
+ outb(msb, dev->iobase + DAS08AO_AO_MSB(chan));
+ /* load DACs */
+ inb(dev->iobase + DAS08AO_AO_UPDATE);
+ }
+}
+
+static int das08_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ das08_ao_set_data(dev, chan, val);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+int das08_common_attach(struct comedi_device *dev, unsigned long iobase)
+{
+ const struct das08_board_struct *thisboard = dev->board_ptr;
+ struct das08_private_struct *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ dev->iobase = iobase;
+
+ dev->board_name = thisboard->name;
+
+ ret = comedi_alloc_subdevices(dev, 6);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai */
+ if (thisboard->ai_nbits) {
+ s->type = COMEDI_SUBD_AI;
+ /* XXX some boards actually have differential
+ * inputs instead of single ended.
+ * The driver does nothing with arefs though,
+ * so it's no big deal.
+ */
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->maxdata = (1 << thisboard->ai_nbits) - 1;
+ s->range_table = das08_ai_lranges[thisboard->ai_pg];
+ s->insn_read = das08_ai_rinsn;
+ devpriv->pg_gainlist = das08_gainlists[thisboard->ai_pg];
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[1];
+ /* ao */
+ if (thisboard->ao_nbits) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = (1 << thisboard->ao_nbits) - 1;
+ s->range_table = &range_bipolar5;
+ s->insn_write = das08_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* initialize all channels to 0V */
+ for (i = 0; i < s->n_chan; i++) {
+ s->readback[i] = s->maxdata / 2;
+ das08_ao_set_data(dev, i, s->readback[i]);
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* di */
+ if (thisboard->di_nchan) {
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = thisboard->di_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits =
+ thisboard->is_jr ? das08jr_di_rbits : das08_di_rbits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[3];
+ /* do */
+ if (thisboard->do_nchan) {
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = thisboard->do_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits =
+ thisboard->is_jr ? das08jr_do_wbits : das08_do_wbits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[4];
+ /* 8255 */
+ if (thisboard->i8255_offset != 0) {
+ ret = subdev_8255_init(dev, s, NULL, thisboard->i8255_offset);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Counter subdevice (8254) */
+ s = &dev->subdevices[5];
+ if (thisboard->i8254_offset) {
+ dev->pacer = comedi_8254_init(dev->iobase +
+ thisboard->i8254_offset,
+ 0, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ comedi_8254_subdevice_init(s, dev->pacer);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(das08_common_attach);
+
+static int __init das08_init(void)
+{
+ return 0;
+}
+module_init(das08_init);
+
+static void __exit das08_exit(void)
+{
+}
+module_exit(das08_exit);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das08.h b/drivers/staging/comedi/drivers/das08.h
new file mode 100644
index 000000000..f86167da5
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08.h
@@ -0,0 +1,51 @@
+/*
+ das08.h
+
+ Header for das08.c and das08_cs.c
+
+ Copyright (C) 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _DAS08_H
+#define _DAS08_H
+
+/* different ways ai data is encoded in first two registers */
+enum das08_ai_encoding { das08_encode12, das08_encode16, das08_pcm_encode12 };
+enum das08_lrange { das08_pg_none, das08_bipolar5, das08_pgh, das08_pgl,
+ das08_pgm
+};
+
+struct das08_board_struct {
+ const char *name;
+ bool is_jr; /* true for 'JR' boards */
+ unsigned int ai_nbits;
+ enum das08_lrange ai_pg;
+ enum das08_ai_encoding ai_encoding;
+ unsigned int ao_nbits;
+ unsigned int di_nchan;
+ unsigned int do_nchan;
+ unsigned int i8255_offset;
+ unsigned int i8254_offset;
+ unsigned int iosize; /* number of ioports used */
+};
+
+struct das08_private_struct {
+ unsigned int do_mux_bits; /* bits for do/mux register on boards
+ * without separate do register
+ */
+ const unsigned int *pg_gainlist;
+};
+
+int das08_common_attach(struct comedi_device *dev, unsigned long iobase);
+
+#endif /* _DAS08_H */
diff --git a/drivers/staging/comedi/drivers/das08_cs.c b/drivers/staging/comedi/drivers/das08_cs.c
new file mode 100644
index 000000000..93fab6890
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08_cs.c
@@ -0,0 +1,114 @@
+/*
+ comedi/drivers/das08_cs.c
+ DAS08 driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ PCMCIA support code for this driver is adapted from the dummy_cs.c
+ driver of the Linux PCMCIA Card Services package.
+
+ The initial developer of the original code is David A. Hinds
+ <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
+ are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
+*/
+/*
+Driver: das08_cs
+Description: DAS-08 PCMCIA boards
+Author: Warren Jasper, ds, Frank Hess
+Devices: [ComputerBoards] PCM-DAS08 (pcm-das08)
+Status: works
+
+This is the PCMCIA-specific support split off from the
+das08 driver.
+
+Options (for pcm-das08):
+ NONE
+
+Command support does not exist, but could be added for this board.
+*/
+
+#include <linux/module.h>
+
+#include "../comedi_pcmcia.h"
+
+#include "das08.h"
+
+static const struct das08_board_struct das08_cs_boards[] = {
+ {
+ .name = "pcm-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_bipolar5,
+ .ai_encoding = das08_pcm_encode12,
+ .di_nchan = 3,
+ .do_nchan = 3,
+ .iosize = 16,
+ },
+};
+
+static int das08_cs_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct das08_private_struct *devpriv;
+ unsigned long iobase;
+ int ret;
+
+ /* The das08 driver needs the board_ptr */
+ dev->board_ptr = &das08_cs_boards[0];
+
+ link->config_flags |= CONF_AUTO_SET_IO;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ iobase = link->resource[0]->start;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ return das08_common_attach(dev, iobase);
+}
+
+static struct comedi_driver driver_das08_cs = {
+ .driver_name = "das08_cs",
+ .module = THIS_MODULE,
+ .auto_attach = das08_cs_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int das08_pcmcia_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_das08_cs);
+}
+
+static const struct pcmcia_device_id das08_cs_id_table[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4001),
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, das08_cs_id_table);
+
+static struct pcmcia_driver das08_cs_driver = {
+ .name = "pcm-das08",
+ .owner = THIS_MODULE,
+ .id_table = das08_cs_id_table,
+ .probe = das08_pcmcia_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_das08_cs, das08_cs_driver);
+
+MODULE_AUTHOR("David A. Schleef <ds@schleef.org>, "
+ "Frank Mori Hess <fmhess@users.sourceforge.net>");
+MODULE_DESCRIPTION("Comedi driver for ComputerBoards DAS-08 PCMCIA boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das08_isa.c b/drivers/staging/comedi/drivers/das08_isa.c
new file mode 100644
index 000000000..2d9a31dab
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08_isa.c
@@ -0,0 +1,199 @@
+/*
+ * das08_isa.c
+ * comedi driver for DAS08 ISA/PC-104 boards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: das08_isa
+ * Description: DAS-08 ISA/PC-104 compatible boards
+ * Devices: [Keithley Metrabyte] DAS08 (isa-das08),
+ * [ComputerBoards] DAS08 (isa-das08), DAS08-PGM (das08-pgm),
+ * DAS08-PGH (das08-pgh), DAS08-PGL (das08-pgl), DAS08-AOH (das08-aoh),
+ * DAS08-AOL (das08-aol), DAS08-AOM (das08-aom), DAS08/JR-AO (das08/jr-ao),
+ * DAS08/JR-16-AO (das08jr-16-ao), PC104-DAS08 (pc104-das08),
+ * DAS08/JR/16 (das08jr/16)
+ * Author: Warren Jasper, ds, Frank Hess
+ * Updated: Fri, 31 Aug 2012 19:19:06 +0100
+ * Status: works
+ *
+ * This is the ISA/PC-104-specific support split off from the das08 driver.
+ *
+ * Configuration Options:
+ * [0] - base io address
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "das08.h"
+
+static const struct das08_board_struct das08_isa_boards[] = {
+ {
+ /* cio-das08.pdf */
+ .name = "isa-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 8,
+ .i8254_offset = 4,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08pgx.pdf */
+ .name = "das08-pgm",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgm,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08pgx.pdf */
+ .name = "das08-pgh",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgh,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08pgx.pdf */
+ .name = "das08-pgl",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgl,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08_aox.pdf */
+ .name = "das08-aoh",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgh,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0x0c,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08_aox.pdf */
+ .name = "das08-aol",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgl,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0x0c,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08_aox.pdf */
+ .name = "das08-aom",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgm,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0x0c,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08-jr-ao.pdf */
+ .name = "das08/jr-ao",
+ .is_jr = true,
+ .ai_nbits = 12,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08jr-16-ao.pdf */
+ .name = "das08jr-16-ao",
+ .is_jr = true,
+ .ai_nbits = 16,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode16,
+ .ao_nbits = 16,
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ .name = "pc104-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 4,
+ .iosize = 16, /* unchecked */
+ }, {
+ .name = "das08jr/16",
+ .is_jr = true,
+ .ai_nbits = 16,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode16,
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .iosize = 16, /* unchecked */
+ },
+};
+
+static int das08_isa_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct das08_board_struct *thisboard = dev->board_ptr;
+ struct das08_private_struct *devpriv;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], thisboard->iosize);
+ if (ret)
+ return ret;
+
+ return das08_common_attach(dev, dev->iobase);
+}
+
+static struct comedi_driver das08_isa_driver = {
+ .driver_name = "isa-das08",
+ .module = THIS_MODULE,
+ .attach = das08_isa_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &das08_isa_boards[0].name,
+ .num_names = ARRAY_SIZE(das08_isa_boards),
+ .offset = sizeof(das08_isa_boards[0]),
+};
+module_comedi_driver(das08_isa_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das08_pci.c b/drivers/staging/comedi/drivers/das08_pci.c
new file mode 100644
index 000000000..d8d27fa44
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08_pci.c
@@ -0,0 +1,105 @@
+/*
+ * das08_pci.c
+ * comedi driver for DAS08 PCI boards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: das08_pci
+ * Description: DAS-08 PCI compatible boards
+ * Devices: [ComputerBoards] PCI-DAS08 (pci-das08)
+ * Author: Warren Jasper, ds, Frank Hess
+ * Updated: Fri, 31 Aug 2012 19:19:06 +0100
+ * Status: works
+ *
+ * This is the PCI-specific support split off from the das08 driver.
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+#include "das08.h"
+
+static const struct das08_board_struct das08_pci_boards[] = {
+ {
+ .name = "pci-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_bipolar5,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 4,
+ .iosize = 8,
+ },
+};
+
+static int das08_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pdev = comedi_to_pci_dev(dev);
+ struct das08_private_struct *devpriv;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* The das08 driver needs the board_ptr */
+ dev->board_ptr = &das08_pci_boards[0];
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pdev, 2);
+
+ return das08_common_attach(dev, dev->iobase);
+}
+
+static struct comedi_driver das08_pci_comedi_driver = {
+ .driver_name = "pci-das08",
+ .module = THIS_MODULE,
+ .auto_attach = das08_pci_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int das08_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &das08_pci_comedi_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id das08_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0029) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, das08_pci_table);
+
+static struct pci_driver das08_pci_driver = {
+ .name = "pci-das08",
+ .id_table = das08_pci_table,
+ .probe = das08_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(das08_pci_comedi_driver, das08_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das16.c b/drivers/staging/comedi/drivers/das16.c
new file mode 100644
index 000000000..d7cf4b153
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das16.c
@@ -0,0 +1,1207 @@
+/*
+ * das16.c
+ * DAS16 driver
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2000 Chris R. Baugher <baugher@enteract.com>
+ * Copyright (C) 2001,2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: das16
+ * Description: DAS16 compatible boards
+ * Author: Sam Moore, Warren Jasper, ds, Chris Baugher, Frank Hess, Roman Fietze
+ * Devices: [Keithley Metrabyte] DAS-16 (das-16), DAS-16G (das-16g),
+ * DAS-16F (das-16f), DAS-1201 (das-1201), DAS-1202 (das-1202),
+ * DAS-1401 (das-1401), DAS-1402 (das-1402), DAS-1601 (das-1601),
+ * DAS-1602 (das-1602),
+ * [ComputerBoards] PC104-DAS16/JR (pc104-das16jr),
+ * PC104-DAS16JR/16 (pc104-das16jr/16), CIO-DAS16 (cio-das16),
+ * CIO-DAS16F (cio-das16/f), CIO-DAS16/JR (cio-das16/jr),
+ * CIO-DAS16JR/16 (cio-das16jr/16), CIO-DAS1401/12 (cio-das1401/12),
+ * CIO-DAS1402/12 (cio-das1402/12), CIO-DAS1402/16 (cio-das1402/16),
+ * CIO-DAS1601/12 (cio-das1601/12), CIO-DAS1602/12 (cio-das1602/12),
+ * CIO-DAS1602/16 (cio-das1602/16), CIO-DAS16/330 (cio-das16/330)
+ * Status: works
+ * Updated: 2003-10-12
+ *
+ * A rewrite of the das16 and das1600 drivers.
+ *
+ * Options:
+ * [0] - base io address
+ * [1] - irq (does nothing, irq is not used anymore)
+ * [2] - dma channel (optional, required for comedi_command support)
+ * [3] - master clock speed in MHz (optional, 1 or 10, ignored if
+ * board can probe clock, defaults to 1)
+ * [4] - analog input range lowest voltage in microvolts (optional,
+ * only useful if your board does not have software
+ * programmable gain)
+ * [5] - analog input range highest voltage in microvolts (optional,
+ * only useful if board does not have software programmable
+ * gain)
+ * [6] - analog output range lowest voltage in microvolts (optional)
+ * [7] - analog output range highest voltage in microvolts (optional)
+ *
+ * Passing a zero for an option is the same as leaving it unspecified.
+ */
+
+/*
+ * Testing and debugging help provided by Daniel Koch.
+ *
+ * Keithley Manuals:
+ * 2309.PDF (das16)
+ * 4919.PDF (das1400, 1600)
+ * 4922.PDF (das-1400)
+ * 4923.PDF (das1200, 1400, 1600)
+ *
+ * Computer boards manuals also available from their website
+ * www.measurementcomputing.com
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+#include "8255.h"
+
+#define DAS16_DMA_SIZE 0xff00 /* size in bytes of allocated dma buffer */
+
+/*
+ * Register I/O map
+ */
+#define DAS16_TRIG_REG 0x00
+#define DAS16_AI_LSB_REG 0x00
+#define DAS16_AI_MSB_REG 0x01
+#define DAS16_MUX_REG 0x02
+#define DAS16_DIO_REG 0x03
+#define DAS16_AO_LSB_REG(x) ((x) ? 0x06 : 0x04)
+#define DAS16_AO_MSB_REG(x) ((x) ? 0x07 : 0x05)
+#define DAS16_STATUS_REG 0x08
+#define DAS16_STATUS_BUSY (1 << 7)
+#define DAS16_STATUS_UNIPOLAR (1 << 6)
+#define DAS16_STATUS_MUXBIT (1 << 5)
+#define DAS16_STATUS_INT (1 << 4)
+#define DAS16_CTRL_REG 0x09
+#define DAS16_CTRL_INTE (1 << 7)
+#define DAS16_CTRL_IRQ(x) (((x) & 0x7) << 4)
+#define DAS16_CTRL_DMAE (1 << 2)
+#define DAS16_CTRL_PACING_MASK (3 << 0)
+#define DAS16_CTRL_INT_PACER (3 << 0)
+#define DAS16_CTRL_EXT_PACER (2 << 0)
+#define DAS16_CTRL_SOFT_PACER (0 << 0)
+#define DAS16_PACER_REG 0x0a
+#define DAS16_PACER_BURST_LEN(x) (((x) & 0xf) << 4)
+#define DAS16_PACER_CTR0 (1 << 1)
+#define DAS16_PACER_TRIG0 (1 << 0)
+#define DAS16_GAIN_REG 0x0b
+#define DAS16_TIMER_BASE_REG 0x0c /* to 0x0f */
+
+#define DAS1600_CONV_REG 0x404
+#define DAS1600_CONV_DISABLE (1 << 6)
+#define DAS1600_BURST_REG 0x405
+#define DAS1600_BURST_VAL (1 << 6)
+#define DAS1600_ENABLE_REG 0x406
+#define DAS1600_ENABLE_VAL (1 << 6)
+#define DAS1600_STATUS_REG 0x407
+#define DAS1600_STATUS_BME (1 << 6)
+#define DAS1600_STATUS_ME (1 << 5)
+#define DAS1600_STATUS_CD (1 << 4)
+#define DAS1600_STATUS_WS (1 << 1)
+#define DAS1600_STATUS_CLK_10MHZ (1 << 0)
+
+static const struct comedi_lrange range_das1x01_bip = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_das1x01_unip = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_das1x02_bip = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_das1x02_unip = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_das16jr = {
+ 9, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_das16jr_16 = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const int das16jr_gainlist[] = { 8, 0, 1, 2, 3, 4, 5, 6, 7 };
+static const int das16jr_16_gainlist[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
+static const int das1600_gainlist[] = { 0, 1, 2, 3 };
+
+enum {
+ das16_pg_none = 0,
+ das16_pg_16jr,
+ das16_pg_16jr_16,
+ das16_pg_1601,
+ das16_pg_1602,
+};
+static const int *const das16_gainlists[] = {
+ NULL,
+ das16jr_gainlist,
+ das16jr_16_gainlist,
+ das1600_gainlist,
+ das1600_gainlist,
+};
+
+static const struct comedi_lrange *const das16_ai_uni_lranges[] = {
+ &range_unknown,
+ &range_das16jr,
+ &range_das16jr_16,
+ &range_das1x01_unip,
+ &range_das1x02_unip,
+};
+
+static const struct comedi_lrange *const das16_ai_bip_lranges[] = {
+ &range_unknown,
+ &range_das16jr,
+ &range_das16jr_16,
+ &range_das1x01_bip,
+ &range_das1x02_bip,
+};
+
+struct das16_board {
+ const char *name;
+ unsigned int ai_maxdata;
+ unsigned int ai_speed; /* max conversion speed in nanosec */
+ unsigned int ai_pg;
+ unsigned int has_ao:1;
+ unsigned int has_8255:1;
+
+ unsigned int i8255_offset;
+
+ unsigned int size;
+ unsigned int id;
+};
+
+static const struct das16_board das16_boards[] = {
+ {
+ .name = "das-16",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 15000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x00,
+ }, {
+ .name = "das-16g",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 15000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x00,
+ }, {
+ .name = "das-16f",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 8500,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x00,
+ }, {
+ .name = "cio-das16",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 20000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x80,
+ }, {
+ .name = "cio-das16/f",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x80,
+ }, {
+ .name = "cio-das16/jr",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 7692,
+ .ai_pg = das16_pg_16jr,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "pc104-das16jr",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 3300,
+ .ai_pg = das16_pg_16jr,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "cio-das16jr/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_16jr_16,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "pc104-das16jr/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_16jr_16,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "das-1201",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 20000,
+ .ai_pg = das16_pg_none,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0x20,
+ }, {
+ .name = "das-1202",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_none,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0x20,
+ }, {
+ .name = "das-1401",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1601,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "das-1402",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "das-1601",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1601,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "das-1602",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1401/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 6250,
+ .ai_pg = das16_pg_1601,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1402/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 6250,
+ .ai_pg = das16_pg_1602,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1402/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1601/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 6250,
+ .ai_pg = das16_pg_1601,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1602/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1602/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das16/330",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 3030,
+ .ai_pg = das16_pg_16jr,
+ .size = 0x14,
+ .id = 0xf0,
+ },
+};
+
+/* Period for timer interrupt in jiffies. It's a function
+ * to deal with possibility of dynamic HZ patches */
+static inline int timer_period(void)
+{
+ return HZ / 20;
+}
+
+struct das16_private_struct {
+ struct comedi_isadma *dma;
+ unsigned int clockbase;
+ unsigned int ctrl_reg;
+ unsigned int divisor1;
+ unsigned int divisor2;
+ struct timer_list timer;
+ unsigned long extra_iobase;
+ unsigned int can_burst:1;
+ unsigned int timer_running:1;
+};
+
+static void das16_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int unread_samples)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize);
+ unsigned int nsamples;
+
+ /*
+ * Determine dma size based on the buffer size plus the number of
+ * unread samples and the number of samples remaining in the command.
+ */
+ nsamples = comedi_nsamples_left(s, max_samples + unread_samples);
+ if (nsamples > unread_samples) {
+ nsamples -= unread_samples;
+ desc->size = comedi_samples_to_bytes(s, nsamples);
+ comedi_isadma_program(desc);
+ }
+}
+
+static void das16_interrupt(struct comedi_device *dev)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned long spin_flags;
+ unsigned int residue;
+ unsigned int nbytes;
+ unsigned int nsamples;
+
+ spin_lock_irqsave(&dev->spinlock, spin_flags);
+ if (!(devpriv->ctrl_reg & DAS16_CTRL_DMAE)) {
+ spin_unlock_irqrestore(&dev->spinlock, spin_flags);
+ return;
+ }
+
+ /*
+ * The pc104-das16jr (at least) has problems if the dma
+ * transfer is interrupted in the middle of transferring
+ * a 16 bit sample.
+ */
+ residue = comedi_isadma_disable_on_sample(desc->chan,
+ comedi_bytes_per_sample(s));
+
+ /* figure out how many samples to read */
+ if (residue > desc->size) {
+ dev_err(dev->class_dev, "residue > transfer size!\n");
+ async->events |= COMEDI_CB_ERROR;
+ nbytes = 0;
+ } else {
+ nbytes = desc->size - residue;
+ }
+ nsamples = comedi_bytes_to_samples(s, nbytes);
+
+ /* restart DMA if more samples are needed */
+ if (nsamples) {
+ dma->cur_dma = 1 - dma->cur_dma;
+ das16_ai_setup_dma(dev, s, nsamples);
+ }
+
+ spin_unlock_irqrestore(&dev->spinlock, spin_flags);
+
+ comedi_buf_write_samples(s, desc->virt_addr, nsamples);
+
+ if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+
+ comedi_handle_events(dev, s);
+}
+
+static void das16_timer_interrupt(unsigned long arg)
+{
+ struct comedi_device *dev = (struct comedi_device *)arg;
+ struct das16_private_struct *devpriv = dev->private;
+ unsigned long flags;
+
+ das16_interrupt(dev);
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (devpriv->timer_running)
+ mod_timer(&devpriv->timer, jiffies + timer_period());
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static void das16_ai_set_mux_range(struct comedi_device *dev,
+ unsigned int first_chan,
+ unsigned int last_chan,
+ unsigned int range)
+{
+ const struct das16_board *board = dev->board_ptr;
+
+ /* set multiplexer */
+ outb(first_chan | (last_chan << 4), dev->iobase + DAS16_MUX_REG);
+
+ /* some boards do not have programmable gain */
+ if (board->ai_pg == das16_pg_none)
+ return;
+
+ /*
+ * Set gain (this is also burst rate register but according to
+ * computer boards manual, burst rate does nothing, even on
+ * keithley cards).
+ */
+ outb((das16_gainlists[board->ai_pg])[range],
+ dev->iobase + DAS16_GAIN_REG);
+}
+
+static int das16_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != ((chan0 + i) % s->n_chan)) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct das16_board *board = dev->board_ptr;
+ struct das16_private_struct *devpriv = dev->private;
+ int err = 0;
+ unsigned int trig_mask;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+
+ trig_mask = TRIG_FOLLOW;
+ if (devpriv->can_burst)
+ trig_mask |= TRIG_TIMER | TRIG_EXT;
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, trig_mask);
+
+ trig_mask = TRIG_TIMER | TRIG_EXT;
+ if (devpriv->can_burst)
+ trig_mask |= TRIG_NOW;
+ err |= comedi_check_trigger_src(&cmd->convert_src, trig_mask);
+
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /* make sure scan_begin_src and convert_src dont conflict */
+ if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW)
+ err |= -EINVAL;
+ if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ /* check against maximum frequency */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ board->ai_speed *
+ cmd->chanlist_len);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_speed);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up arguments */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das16_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static unsigned int das16_set_pacer(struct comedi_device *dev, unsigned int ns,
+ unsigned int flags)
+{
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &ns, flags);
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+
+ return ns;
+}
+
+static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int first_chan = CR_CHAN(cmd->chanlist[0]);
+ unsigned int last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]);
+ unsigned int range = CR_RANGE(cmd->chanlist[0]);
+ unsigned int byte;
+ unsigned long flags;
+
+ if (cmd->flags & CMDF_PRIORITY) {
+ dev_err(dev->class_dev,
+ "isa dma transfers cannot be performed with CMDF_PRIORITY, aborting\n");
+ return -1;
+ }
+
+ if (devpriv->can_burst)
+ outb(DAS1600_CONV_DISABLE, dev->iobase + DAS1600_CONV_REG);
+
+ /* set mux and range for chanlist scan */
+ das16_ai_set_mux_range(dev, first_chan, last_chan, range);
+
+ /* set counter mode and counts */
+ cmd->convert_arg = das16_set_pacer(dev, cmd->convert_arg, cmd->flags);
+
+ /* enable counters */
+ byte = 0;
+ if (devpriv->can_burst) {
+ if (cmd->convert_src == TRIG_NOW) {
+ outb(DAS1600_BURST_VAL,
+ dev->iobase + DAS1600_BURST_REG);
+ /* set burst length */
+ byte |= DAS16_PACER_BURST_LEN(cmd->chanlist_len - 1);
+ } else {
+ outb(0, dev->iobase + DAS1600_BURST_REG);
+ }
+ }
+ outb(byte, dev->iobase + DAS16_PACER_REG);
+
+ /* set up dma transfer */
+ dma->cur_dma = 0;
+ das16_ai_setup_dma(dev, s, 0);
+
+ /* set up timer */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->timer_running = 1;
+ devpriv->timer.expires = jiffies + timer_period();
+ add_timer(&devpriv->timer);
+
+ /* enable DMA interrupt with external or internal pacing */
+ devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE | DAS16_CTRL_PACING_MASK);
+ devpriv->ctrl_reg |= DAS16_CTRL_DMAE;
+ if (cmd->convert_src == TRIG_EXT)
+ devpriv->ctrl_reg |= DAS16_CTRL_EXT_PACER;
+ else
+ devpriv->ctrl_reg |= DAS16_CTRL_INT_PACER;
+ outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG);
+
+ if (devpriv->can_burst)
+ outb(0, dev->iobase + DAS1600_CONV_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return 0;
+}
+
+static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ /* disable interrupts, dma and pacer clocked conversions */
+ devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE | DAS16_CTRL_DMAE |
+ DAS16_CTRL_PACING_MASK);
+ outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG);
+
+ comedi_isadma_disable(dma->chan);
+
+ /* disable SW timer */
+ if (devpriv->timer_running) {
+ devpriv->timer_running = 0;
+ del_timer(&devpriv->timer);
+ }
+
+ if (devpriv->can_burst)
+ outb(0, dev->iobase + DAS1600_BURST_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return 0;
+}
+
+static void das16_ai_munge(struct comedi_device *dev,
+ struct comedi_subdevice *s, void *array,
+ unsigned int num_bytes,
+ unsigned int start_chan_index)
+{
+ unsigned short *data = array;
+ unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes);
+ unsigned int i;
+
+ for (i = 0; i < num_samples; i++) {
+ data[i] = le16_to_cpu(data[i]);
+ if (s->maxdata == 0x0fff)
+ data[i] >>= 4;
+ data[i] &= s->maxdata;
+ }
+}
+
+static int das16_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS16_STATUS_REG);
+ if ((status & DAS16_STATUS_BUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int das16_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int ret;
+ int i;
+
+ /* set mux and range for single channel */
+ das16_ai_set_mux_range(dev, chan, chan, range);
+
+ for (i = 0; i < insn->n; i++) {
+ /* trigger conversion */
+ outb_p(0, dev->iobase + DAS16_TRIG_REG);
+
+ ret = comedi_timeout(dev, s, insn, das16_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(dev->iobase + DAS16_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + DAS16_AI_LSB_REG);
+ if (s->maxdata == 0x0fff)
+ val >>= 4;
+ val &= s->maxdata;
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int das16_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ val <<= 4;
+
+ outb(val & 0xff, dev->iobase + DAS16_AO_LSB_REG(chan));
+ outb((val >> 8) & 0xff, dev->iobase + DAS16_AO_MSB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int das16_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + DAS16_DIO_REG) & 0xf;
+
+ return insn->n;
+}
+
+static int das16_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS16_DIO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int das16_probe(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct das16_board *board = dev->board_ptr;
+ int diobits;
+
+ /* diobits indicates boards */
+ diobits = inb(dev->iobase + DAS16_DIO_REG) & 0xf0;
+ if (board->id != diobits) {
+ dev_err(dev->class_dev,
+ "requested board's id bits are incorrect (0x%x != 0x%x)\n",
+ board->id, diobits);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void das16_reset(struct comedi_device *dev)
+{
+ outb(0, dev->iobase + DAS16_STATUS_REG);
+ outb(0, dev->iobase + DAS16_CTRL_REG);
+ outb(0, dev->iobase + DAS16_PACER_REG);
+}
+
+static void das16_alloc_dma(struct comedi_device *dev, unsigned int dma_chan)
+{
+ struct das16_private_struct *devpriv = dev->private;
+
+ /* only DMA channels 3 and 1 are valid */
+ if (!(dma_chan == 1 || dma_chan == 3))
+ return;
+
+ /* DMA uses two buffers */
+ devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan,
+ DAS16_DMA_SIZE, COMEDI_ISADMA_READ);
+ if (devpriv->dma) {
+ setup_timer(&devpriv->timer, das16_timer_interrupt,
+ (unsigned long)dev);
+ }
+}
+
+static void das16_free_dma(struct comedi_device *dev)
+{
+ struct das16_private_struct *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->timer.data)
+ del_timer_sync(&devpriv->timer);
+ comedi_isadma_free(devpriv->dma);
+ }
+}
+
+static const struct comedi_lrange *das16_ai_range(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_devconfig *it,
+ unsigned int pg_type,
+ unsigned int status)
+{
+ unsigned int min = it->options[4];
+ unsigned int max = it->options[5];
+
+ /* get any user-defined input range */
+ if (pg_type == das16_pg_none && (min || max)) {
+ struct comedi_lrange *lrange;
+ struct comedi_krange *krange;
+
+ /* allocate single-range range table */
+ lrange = comedi_alloc_spriv(s,
+ sizeof(*lrange) + sizeof(*krange));
+ if (!lrange)
+ return &range_unknown;
+
+ /* initialize ai range */
+ lrange->length = 1;
+ krange = lrange->range;
+ krange->min = min;
+ krange->max = max;
+ krange->flags = UNIT_volt;
+
+ return lrange;
+ }
+
+ /* use software programmable range */
+ if (status & DAS16_STATUS_UNIPOLAR)
+ return das16_ai_uni_lranges[pg_type];
+ return das16_ai_bip_lranges[pg_type];
+}
+
+static const struct comedi_lrange *das16_ao_range(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_devconfig *it)
+{
+ unsigned int min = it->options[6];
+ unsigned int max = it->options[7];
+
+ /* get any user-defined output range */
+ if (min || max) {
+ struct comedi_lrange *lrange;
+ struct comedi_krange *krange;
+
+ /* allocate single-range range table */
+ lrange = comedi_alloc_spriv(s,
+ sizeof(*lrange) + sizeof(*krange));
+ if (!lrange)
+ return &range_unknown;
+
+ /* initialize ao range */
+ lrange->length = 1;
+ krange = lrange->range;
+ krange->min = min;
+ krange->max = max;
+ krange->flags = UNIT_volt;
+
+ return lrange;
+ }
+
+ return &range_unknown;
+}
+
+static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct das16_board *board = dev->board_ptr;
+ struct das16_private_struct *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int osc_base;
+ unsigned int status;
+ int ret;
+
+ /* check that clock setting is valid */
+ if (it->options[3]) {
+ if (it->options[3] != 0 &&
+ it->options[3] != 1 && it->options[3] != 10) {
+ dev_err(dev->class_dev,
+ "Invalid option. Master clock must be set to 1 or 10 (MHz)\n");
+ return -EINVAL;
+ }
+ }
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ if (board->size < 0x400) {
+ ret = comedi_request_region(dev, it->options[0], board->size);
+ if (ret)
+ return ret;
+ } else {
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+ /* Request an additional region for the 8255 */
+ ret = __comedi_request_region(dev, dev->iobase + 0x400,
+ board->size & 0x3ff);
+ if (ret)
+ return ret;
+ devpriv->extra_iobase = dev->iobase + 0x400;
+ devpriv->can_burst = 1;
+ }
+
+ /* probe id bits to make sure they are consistent */
+ if (das16_probe(dev, it))
+ return -EINVAL;
+
+ /* get master clock speed */
+ osc_base = I8254_OSC_BASE_1MHZ;
+ if (devpriv->can_burst) {
+ status = inb(dev->iobase + DAS1600_STATUS_REG);
+ if (status & DAS1600_STATUS_CLK_10MHZ)
+ osc_base = I8254_OSC_BASE_10MHZ;
+ } else {
+ if (it->options[3])
+ osc_base = I8254_OSC_BASE_1MHZ / it->options[3];
+ }
+
+ dev->pacer = comedi_8254_init(dev->iobase + DAS16_TIMER_BASE_REG,
+ osc_base, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ das16_alloc_dma(dev, it->options[2]);
+
+ ret = comedi_alloc_subdevices(dev, 4 + board->has_8255);
+ if (ret)
+ return ret;
+
+ status = inb(dev->iobase + DAS16_STATUS_REG);
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if (status & DAS16_STATUS_MUXBIT) {
+ s->subdev_flags |= SDF_GROUND;
+ s->n_chan = 16;
+ } else {
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = 8;
+ }
+ s->len_chanlist = s->n_chan;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = das16_ai_range(dev, s, it, board->ai_pg, status);
+ s->insn_read = das16_ai_insn_read;
+ if (devpriv->dma) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmdtest = das16_cmd_test;
+ s->do_cmd = das16_cmd_exec;
+ s->cancel = das16_cancel;
+ s->munge = das16_ai_munge;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ if (board->has_ao) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = das16_ao_range(dev, s, it);
+ s->insn_write = das16_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das16_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das16_do_insn_bits;
+
+ /* initialize digital output lines */
+ outb(s->state, dev->iobase + DAS16_DIO_REG);
+
+ /* 8255 Digital I/O subdevice */
+ if (board->has_8255) {
+ s = &dev->subdevices[4];
+ ret = subdev_8255_init(dev, s, NULL, board->i8255_offset);
+ if (ret)
+ return ret;
+ }
+
+ das16_reset(dev);
+ /* set the interrupt level */
+ devpriv->ctrl_reg = DAS16_CTRL_IRQ(dev->irq);
+ outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG);
+
+ if (devpriv->can_burst) {
+ outb(DAS1600_ENABLE_VAL, dev->iobase + DAS1600_ENABLE_REG);
+ outb(0, dev->iobase + DAS1600_CONV_REG);
+ outb(0, dev->iobase + DAS1600_BURST_REG);
+ }
+
+ return 0;
+}
+
+static void das16_detach(struct comedi_device *dev)
+{
+ const struct das16_board *board = dev->board_ptr;
+ struct das16_private_struct *devpriv = dev->private;
+
+ if (devpriv) {
+ if (dev->iobase)
+ das16_reset(dev);
+ das16_free_dma(dev);
+
+ if (devpriv->extra_iobase)
+ release_region(devpriv->extra_iobase,
+ board->size & 0x3ff);
+ }
+
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver das16_driver = {
+ .driver_name = "das16",
+ .module = THIS_MODULE,
+ .attach = das16_attach,
+ .detach = das16_detach,
+ .board_name = &das16_boards[0].name,
+ .num_names = ARRAY_SIZE(das16_boards),
+ .offset = sizeof(das16_boards[0]),
+};
+module_comedi_driver(das16_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for DAS16 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das16m1.c b/drivers/staging/comedi/drivers/das16m1.c
new file mode 100644
index 000000000..1adf6a71a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das16m1.c
@@ -0,0 +1,644 @@
+/*
+ comedi/drivers/das16m1.c
+ CIO-DAS16/M1 driver
+ Author: Frank Mori Hess, based on code from the das16
+ driver.
+ Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: das16m1
+Description: CIO-DAS16/M1
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1)
+Status: works
+
+This driver supports a single board - the CIO-DAS16/M1.
+As far as I know, there are no other boards that have
+the same register layout. Even the CIO-DAS16/M1/16 is
+significantly different.
+
+I was _barely_ able to reach the full 1 MHz capability
+of this board, using a hard real-time interrupt
+(set the TRIG_RT flag in your struct comedi_cmd and use
+rtlinux or RTAI). The board can't do dma, so the bottleneck is
+pulling the data across the ISA bus. I timed the interrupt
+handler, and it took my computer ~470 microseconds to pull 512
+samples from the board. So at 1 Mhz sampling rate,
+expect your CPU to be spending almost all of its
+time in the interrupt handler.
+
+This board has some unusual restrictions for its channel/gain list. If the
+list has 2 or more channels in it, then two conditions must be satisfied:
+(1) - even/odd channels must appear at even/odd indices in the list
+(2) - the list must have an even number of entries.
+
+Options:
+ [0] - base io address
+ [1] - irq (optional, but you probably want it)
+
+irq can be omitted, although the cmd interface will not work without it.
+*/
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+#include "comedi_8254.h"
+
+#define DAS16M1_SIZE2 8
+
+#define FIFO_SIZE 1024 /* 1024 sample fifo */
+
+/*
+ CIO-DAS16_M1.pdf
+
+ "cio-das16/m1"
+
+ 0 a/d bits 0-3, mux start 12 bit
+ 1 a/d bits 4-11 unused
+ 2 status control
+ 3 di 4 bit do 4 bit
+ 4 unused clear interrupt
+ 5 interrupt, pacer
+ 6 channel/gain queue address
+ 7 channel/gain queue data
+ 89ab 8254
+ cdef 8254
+ 400 8255
+ 404-407 8254
+
+*/
+
+#define DAS16M1_AI 0 /* 16-bit wide register */
+#define AI_CHAN(x) ((x) & 0xf)
+#define DAS16M1_CS 2
+#define EXT_TRIG_BIT 0x1
+#define OVRUN 0x20
+#define IRQDATA 0x80
+#define DAS16M1_DIO 3
+#define DAS16M1_CLEAR_INTR 4
+#define DAS16M1_INTR_CONTROL 5
+#define EXT_PACER 0x2
+#define INT_PACER 0x3
+#define PACER_MASK 0x3
+#define INTE 0x80
+#define DAS16M1_QUEUE_ADDR 6
+#define DAS16M1_QUEUE_DATA 7
+#define Q_CHAN(x) ((x) & 0x7)
+#define Q_RANGE(x) (((x) & 0xf) << 4)
+#define UNIPOLAR 0x40
+#define DAS16M1_8254_FIRST 0x8
+#define DAS16M1_8254_SECOND 0xc
+#define DAS16M1_82C55 0x400
+#define DAS16M1_8254_THIRD 0x404
+
+static const struct comedi_lrange range_das16m1 = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ BIP_RANGE(10)
+ }
+};
+
+struct das16m1_private_struct {
+ struct comedi_8254 *counter;
+ unsigned int control_state;
+ unsigned int adc_count; /* number of samples completed */
+ /* initial value in lower half of hardware conversion counter,
+ * needed to keep track of whether new count has been loaded into
+ * counter yet (loaded by first sample conversion) */
+ u16 initial_hw_count;
+ unsigned short ai_buffer[FIFO_SIZE];
+ unsigned long extra_iobase;
+};
+
+static inline unsigned short munge_sample(unsigned short data)
+{
+ return (data >> 4) & 0xfff;
+}
+
+static void munge_sample_array(unsigned short *array, unsigned int num_elements)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_elements; i++)
+ array[i] = munge_sample(array[i]);
+}
+
+static int das16m1_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int i;
+
+ if (cmd->chanlist_len == 1)
+ return 0;
+
+ if ((cmd->chanlist_len % 2) != 0) {
+ dev_dbg(dev->class_dev,
+ "chanlist must be of even length or length 1\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if ((i % 2) != (chan % 2)) {
+ dev_dbg(dev->class_dev,
+ "even/odd channels must go have even/odd chanlist indices\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int das16m1_cmd_test(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 1000);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ unsigned int arg = cmd->convert_arg;
+
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das16m1_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int das16m1_cmd_exec(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int byte, i;
+
+ /* disable interrupts and internal pacer */
+ devpriv->control_state &= ~INTE & ~PACER_MASK;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ /* set software count */
+ devpriv->adc_count = 0;
+
+ /*
+ * Initialize lower half of hardware counter, used to determine how
+ * many samples are in fifo. Value doesn't actually load into counter
+ * until counter's next clock (the next a/d conversion).
+ */
+ comedi_8254_set_mode(devpriv->counter, 1, I8254_MODE2 | I8254_BINARY);
+ comedi_8254_write(devpriv->counter, 1, 0);
+
+ /*
+ * Remember current reading of counter so we know when counter has
+ * actually been loaded.
+ */
+ devpriv->initial_hw_count = comedi_8254_read(devpriv->counter, 1);
+
+ /* setup channel/gain queue */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ outb(i, dev->iobase + DAS16M1_QUEUE_ADDR);
+ byte =
+ Q_CHAN(CR_CHAN(cmd->chanlist[i])) |
+ Q_RANGE(CR_RANGE(cmd->chanlist[i]));
+ outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
+ }
+
+ /* enable interrupts and set internal pacer counter mode and counts */
+ devpriv->control_state &= ~PACER_MASK;
+ if (cmd->convert_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ devpriv->control_state |= INT_PACER;
+ } else { /* TRIG_EXT */
+ devpriv->control_state |= EXT_PACER;
+ }
+
+ /* set control & status register */
+ byte = 0;
+ /* if we are using external start trigger (also board dislikes having
+ * both start and conversion triggers external simultaneously) */
+ if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT)
+ byte |= EXT_TRIG_BIT;
+
+ outb(byte, dev->iobase + DAS16M1_CS);
+ /* clear interrupt bit */
+ outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
+
+ devpriv->control_state |= INTE;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ return 0;
+}
+
+static int das16m1_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+
+ devpriv->control_state &= ~INTE & ~PACER_MASK;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ return 0;
+}
+
+static int das16m1_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS16M1_CS);
+ if (status & IRQDATA)
+ return 0;
+ return -EBUSY;
+}
+
+static int das16m1_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ int ret;
+ int n;
+ int byte;
+
+ /* disable interrupts and internal pacer */
+ devpriv->control_state &= ~INTE & ~PACER_MASK;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ /* setup channel/gain queue */
+ outb(0, dev->iobase + DAS16M1_QUEUE_ADDR);
+ byte =
+ Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn->chanspec));
+ outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
+
+ for (n = 0; n < insn->n; n++) {
+ /* clear IRQDATA bit */
+ outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
+ /* trigger conversion */
+ outb(0, dev->iobase);
+
+ ret = comedi_timeout(dev, s, insn, das16m1_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[n] = munge_sample(inw(dev->iobase));
+ }
+
+ return n;
+}
+
+static int das16m1_di_rbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int bits;
+
+ bits = inb(dev->iobase + DAS16M1_DIO) & 0xf;
+ data[1] = bits;
+ data[0] = 0;
+
+ return insn->n;
+}
+
+static int das16m1_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS16M1_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void das16m1_handler(struct comedi_device *dev, unsigned int status)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ u16 num_samples;
+ u16 hw_counter;
+
+ s = dev->read_subdev;
+ async = s->async;
+ cmd = &async->cmd;
+
+ /* figure out how many samples are in fifo */
+ hw_counter = comedi_8254_read(devpriv->counter, 1);
+ /* make sure hardware counter reading is not bogus due to initial value
+ * not having been loaded yet */
+ if (devpriv->adc_count == 0 && hw_counter == devpriv->initial_hw_count) {
+ num_samples = 0;
+ } else {
+ /* The calculation of num_samples looks odd, but it uses the following facts.
+ * 16 bit hardware counter is initialized with value of zero (which really
+ * means 0x1000). The counter decrements by one on each conversion
+ * (when the counter decrements from zero it goes to 0xffff). num_samples
+ * is a 16 bit variable, so it will roll over in a similar fashion to the
+ * hardware counter. Work it out, and this is what you get. */
+ num_samples = -hw_counter - devpriv->adc_count;
+ }
+ /* check if we only need some of the points */
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (num_samples > cmd->stop_arg * cmd->chanlist_len)
+ num_samples = cmd->stop_arg * cmd->chanlist_len;
+ }
+ /* make sure we dont try to get too many points if fifo has overrun */
+ if (num_samples > FIFO_SIZE)
+ num_samples = FIFO_SIZE;
+ insw(dev->iobase, devpriv->ai_buffer, num_samples);
+ munge_sample_array(devpriv->ai_buffer, num_samples);
+ comedi_buf_write_samples(s, devpriv->ai_buffer, num_samples);
+ devpriv->adc_count += num_samples;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) {
+ /* end of acquisition */
+ async->events |= COMEDI_CB_EOA;
+ }
+ }
+
+ /* this probably won't catch overruns since the card doesn't generate
+ * overrun interrupts, but we might as well try */
+ if (status & OVRUN) {
+ async->events |= COMEDI_CB_ERROR;
+ dev_err(dev->class_dev, "fifo overflow\n");
+ }
+
+ comedi_handle_events(dev, s);
+}
+
+static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ unsigned long flags;
+ unsigned int status;
+
+ /* prevent race with interrupt handler */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ status = inb(dev->iobase + DAS16M1_CS);
+ das16m1_handler(dev, status);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return comedi_buf_n_bytes_ready(s);
+}
+
+static irqreturn_t das16m1_interrupt(int irq, void *d)
+{
+ int status;
+ struct comedi_device *dev = d;
+
+ if (!dev->attached) {
+ dev_err(dev->class_dev, "premature interrupt\n");
+ return IRQ_HANDLED;
+ }
+ /* prevent race with comedi_poll() */
+ spin_lock(&dev->spinlock);
+
+ status = inb(dev->iobase + DAS16M1_CS);
+
+ if ((status & (IRQDATA | OVRUN)) == 0) {
+ dev_err(dev->class_dev, "spurious interrupt\n");
+ spin_unlock(&dev->spinlock);
+ return IRQ_NONE;
+ }
+
+ das16m1_handler(dev, status);
+
+ /* clear interrupt */
+ outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
+
+ spin_unlock(&dev->spinlock);
+ return IRQ_HANDLED;
+}
+
+static int das16m1_irq_bits(unsigned int irq)
+{
+ switch (irq) {
+ case 10:
+ return 0x0;
+ case 11:
+ return 0x1;
+ case 12:
+ return 0x2;
+ case 15:
+ return 0x3;
+ case 2:
+ return 0x4;
+ case 3:
+ return 0x5;
+ case 5:
+ return 0x6;
+ case 7:
+ return 0x7;
+ default:
+ return 0x0;
+ }
+}
+
+/*
+ * Options list:
+ * 0 I/O base
+ * 1 IRQ
+ */
+static int das16m1_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct das16m1_private_struct *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+ /* Request an additional region for the 8255 */
+ ret = __comedi_request_region(dev, dev->iobase + DAS16M1_82C55,
+ DAS16M1_SIZE2);
+ if (ret)
+ return ret;
+ devpriv->extra_iobase = dev->iobase + DAS16M1_82C55;
+
+ /* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */
+ if ((1 << it->options[1]) & 0xdcfc) {
+ ret = request_irq(it->options[1], das16m1_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ dev->pacer = comedi_8254_init(dev->iobase + DAS16M1_8254_SECOND,
+ I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ devpriv->counter = comedi_8254_init(dev->iobase + DAS16M1_8254_FIRST,
+ 0, I8254_IO8, 0);
+ if (!devpriv->counter)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = 8;
+ s->maxdata = (1 << 12) - 1;
+ s->range_table = &range_das16m1;
+ s->insn_read = das16m1_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 256;
+ s->do_cmdtest = das16m1_cmd_test;
+ s->do_cmd = das16m1_cmd_exec;
+ s->cancel = das16m1_cancel;
+ s->poll = das16m1_poll;
+ }
+
+ s = &dev->subdevices[1];
+ /* di */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das16m1_di_rbits;
+
+ s = &dev->subdevices[2];
+ /* do */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das16m1_do_wbits;
+
+ s = &dev->subdevices[3];
+ /* 8255 */
+ ret = subdev_8255_init(dev, s, NULL, DAS16M1_82C55);
+ if (ret)
+ return ret;
+
+ /* initialize digital output lines */
+ outb(0, dev->iobase + DAS16M1_DIO);
+
+ /* set the interrupt level */
+ devpriv->control_state = das16m1_irq_bits(dev->irq) << 4;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ return 0;
+}
+
+static void das16m1_detach(struct comedi_device *dev)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->extra_iobase)
+ release_region(devpriv->extra_iobase, DAS16M1_SIZE2);
+ kfree(devpriv->counter);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver das16m1_driver = {
+ .driver_name = "das16m1",
+ .module = THIS_MODULE,
+ .attach = das16m1_attach,
+ .detach = das16m1_detach,
+};
+module_comedi_driver(das16m1_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das1800.c b/drivers/staging/comedi/drivers/das1800.c
new file mode 100644
index 000000000..53baf37cd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das1800.c
@@ -0,0 +1,1455 @@
+/*
+ comedi/drivers/das1800.c
+ Driver for Keitley das1700/das1800 series boards
+ Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: das1800
+Description: Keithley Metrabyte DAS1800 (& compatibles)
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+Devices: [Keithley Metrabyte] DAS-1701ST (das-1701st),
+ DAS-1701ST-DA (das-1701st-da), DAS-1701/AO (das-1701ao),
+ DAS-1702ST (das-1702st), DAS-1702ST-DA (das-1702st-da),
+ DAS-1702HR (das-1702hr), DAS-1702HR-DA (das-1702hr-da),
+ DAS-1702/AO (das-1702ao), DAS-1801ST (das-1801st),
+ DAS-1801ST-DA (das-1801st-da), DAS-1801HC (das-1801hc),
+ DAS-1801AO (das-1801ao), DAS-1802ST (das-1802st),
+ DAS-1802ST-DA (das-1802st-da), DAS-1802HR (das-1802hr),
+ DAS-1802HR-DA (das-1802hr-da), DAS-1802HC (das-1802hc),
+ DAS-1802AO (das-1802ao)
+Status: works
+
+The waveform analog output on the 'ao' cards is not supported.
+If you need it, send me (Frank Hess) an email.
+
+Configuration options:
+ [0] - I/O port base address
+ [1] - IRQ (optional, required for timed or externally triggered conversions)
+ [2] - DMA0 (optional, requires irq)
+ [3] - DMA1 (optional, requires irq and dma0)
+*/
+/*
+
+This driver supports the following Keithley boards:
+
+das-1701st
+das-1701st-da
+das-1701ao
+das-1702st
+das-1702st-da
+das-1702hr
+das-1702hr-da
+das-1702ao
+das-1801st
+das-1801st-da
+das-1801hc
+das-1801ao
+das-1802st
+das-1802st-da
+das-1802hr
+das-1802hr-da
+das-1802hc
+das-1802ao
+
+Options:
+ [0] - base io address
+ [1] - irq (optional, required for timed or externally triggered conversions)
+ [2] - dma0 (optional, requires irq)
+ [3] - dma1 (optional, requires irq and dma0)
+
+irq can be omitted, although the cmd interface will not work without it.
+
+analog input cmd triggers supported:
+ start_src: TRIG_NOW | TRIG_EXT
+ scan_begin_src: TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT
+ scan_end_src: TRIG_COUNT
+ convert_src: TRIG_TIMER | TRIG_EXT (TRIG_EXT requires scan_begin_src == TRIG_FOLLOW)
+ stop_src: TRIG_COUNT | TRIG_EXT | TRIG_NONE
+
+scan_begin_src triggers TRIG_TIMER and TRIG_EXT use the card's
+'burst mode' which limits the valid conversion time to 64 microseconds
+(convert_arg <= 64000). This limitation does not apply if scan_begin_src
+is TRIG_FOLLOW.
+
+NOTES:
+Only the DAS-1801ST has been tested by me.
+Unipolar and bipolar ranges cannot be mixed in the channel/gain list.
+
+TODO:
+ Make it automatically allocate irq and dma channels if they are not specified
+ Add support for analog out on 'ao' cards
+ read insn for analog out
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+/* misc. defines */
+#define DAS1800_SIZE 16 /* uses 16 io addresses */
+#define FIFO_SIZE 1024 /* 1024 sample fifo */
+#define UNIPOLAR 0x4 /* bit that determines whether input range is uni/bipolar */
+#define DMA_BUF_SIZE 0x1ff00 /* size in bytes of dma buffers */
+
+/* Registers for the das1800 */
+#define DAS1800_FIFO 0x0
+#define DAS1800_QRAM 0x0
+#define DAS1800_DAC 0x0
+#define DAS1800_SELECT 0x2
+#define ADC 0x0
+#define QRAM 0x1
+#define DAC(a) (0x2 + a)
+#define DAS1800_DIGITAL 0x3
+#define DAS1800_CONTROL_A 0x4
+#define FFEN 0x1
+#define CGEN 0x4
+#define CGSL 0x8
+#define TGEN 0x10
+#define TGSL 0x20
+#define ATEN 0x80
+#define DAS1800_CONTROL_B 0x5
+#define DMA_CH5 0x1
+#define DMA_CH6 0x2
+#define DMA_CH7 0x3
+#define DMA_CH5_CH6 0x5
+#define DMA_CH6_CH7 0x6
+#define DMA_CH7_CH5 0x7
+#define DMA_ENABLED 0x3 /* mask used to determine if dma is enabled */
+#define DMA_DUAL 0x4
+#define IRQ3 0x8
+#define IRQ5 0x10
+#define IRQ7 0x18
+#define IRQ10 0x28
+#define IRQ11 0x30
+#define IRQ15 0x38
+#define FIMD 0x40
+#define DAS1800_CONTROL_C 0X6
+#define IPCLK 0x1
+#define XPCLK 0x3
+#define BMDE 0x4
+#define CMEN 0x8
+#define UQEN 0x10
+#define SD 0x40
+#define UB 0x80
+#define DAS1800_STATUS 0x7
+/* bits that prevent interrupt status bits (and CVEN) from being cleared on write */
+#define CLEAR_INTR_MASK (CVEN_MASK | 0x1f)
+#define INT 0x1
+#define DMATC 0x2
+#define CT0TC 0x8
+#define OVF 0x10
+#define FHF 0x20
+#define FNE 0x40
+#define CVEN_MASK 0x40 /* masks CVEN on write */
+#define CVEN 0x80
+#define DAS1800_BURST_LENGTH 0x8
+#define DAS1800_BURST_RATE 0x9
+#define DAS1800_QRAM_ADDRESS 0xa
+#define DAS1800_COUNTER 0xc
+
+#define IOBASE2 0x400 /* offset of additional ioports used on 'ao' cards */
+
+enum {
+ das1701st, das1701st_da, das1702st, das1702st_da, das1702hr,
+ das1702hr_da,
+ das1701ao, das1702ao, das1801st, das1801st_da, das1802st, das1802st_da,
+ das1802hr, das1802hr_da, das1801hc, das1802hc, das1801ao, das1802ao
+};
+
+/* analog input ranges */
+static const struct comedi_lrange range_ai_das1801 = {
+ 8, {
+ BIP_RANGE(5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02),
+ UNI_RANGE(5),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_ai_das1802 = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+struct das1800_board {
+ const char *name;
+ int ai_speed; /* max conversion period in nanoseconds */
+ int resolution; /* bits of ai resolution */
+ int qram_len; /* length of card's channel / gain queue */
+ int common; /* supports AREF_COMMON flag */
+ int do_n_chan; /* number of digital output channels */
+ int ao_ability; /* 0 == no analog out, 1 == basic analog out, 2 == waveform analog out */
+ int ao_n_chan; /* number of analog out channels */
+ const struct comedi_lrange *range_ai; /* available input ranges */
+};
+
+/* Warning: the maximum conversion speeds listed below are
+ * not always achievable depending on board setup (see
+ * user manual.)
+ */
+static const struct das1800_board das1800_boards[] = {
+ {
+ .name = "das-1701st",
+ .ai_speed = 6250,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 0,
+ .ao_n_chan = 0,
+ .range_ai = &range_ai_das1801,
+ },
+ {
+ .name = "das-1701st-da",
+ .ai_speed = 6250,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 1,
+ .ao_n_chan = 4,
+ .range_ai = &range_ai_das1801,
+ },
+ {
+ .name = "das-1702st",
+ .ai_speed = 6250,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 0,
+ .ao_n_chan = 0,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1702st-da",
+ .ai_speed = 6250,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 1,
+ .ao_n_chan = 4,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1702hr",
+ .ai_speed = 20000,
+ .resolution = 16,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 0,
+ .ao_n_chan = 0,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1702hr-da",
+ .ai_speed = 20000,
+ .resolution = 16,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 1,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1701ao",
+ .ai_speed = 6250,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 2,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1801,
+ },
+ {
+ .name = "das-1702ao",
+ .ai_speed = 6250,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 2,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1801st",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 0,
+ .ao_n_chan = 0,
+ .range_ai = &range_ai_das1801,
+ },
+ {
+ .name = "das-1801st-da",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 0,
+ .ao_n_chan = 4,
+ .range_ai = &range_ai_das1801,
+ },
+ {
+ .name = "das-1802st",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 0,
+ .ao_n_chan = 0,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1802st-da",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 1,
+ .ao_n_chan = 4,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1802hr",
+ .ai_speed = 10000,
+ .resolution = 16,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 0,
+ .ao_n_chan = 0,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1802hr-da",
+ .ai_speed = 10000,
+ .resolution = 16,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 1,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1801hc",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 64,
+ .common = 0,
+ .do_n_chan = 8,
+ .ao_ability = 1,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1801,
+ },
+ {
+ .name = "das-1802hc",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 64,
+ .common = 0,
+ .do_n_chan = 8,
+ .ao_ability = 1,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1802,
+ },
+ {
+ .name = "das-1801ao",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 2,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1801,
+ },
+ {
+ .name = "das-1802ao",
+ .ai_speed = 3000,
+ .resolution = 12,
+ .qram_len = 256,
+ .common = 1,
+ .do_n_chan = 4,
+ .ao_ability = 2,
+ .ao_n_chan = 2,
+ .range_ai = &range_ai_das1802,
+ },
+};
+
+struct das1800_private {
+ struct comedi_isadma *dma;
+ int irq_dma_bits; /* bits for control register b */
+ /* dma bits for control register b, stored so that dma can be
+ * turned on and off */
+ int dma_bits;
+ uint16_t *fifo_buf; /* bounce buffer for analog input FIFO */
+ unsigned long iobase2; /* secondary io address used for analog out on 'ao' boards */
+ unsigned short ao_update_bits; /* remembers the last write to the
+ * 'update' dac */
+};
+
+/* analog out range for 'ao' boards */
+/*
+static const struct comedi_lrange range_ao_2 = {
+ 2, {
+ BIP_RANGE(10),
+ BIP_RANGE(5)
+ }
+};
+*/
+
+static inline uint16_t munge_bipolar_sample(const struct comedi_device *dev,
+ uint16_t sample)
+{
+ const struct das1800_board *thisboard = dev->board_ptr;
+
+ sample += 1 << (thisboard->resolution - 1);
+ return sample;
+}
+
+static void munge_data(struct comedi_device *dev, uint16_t *array,
+ unsigned int num_elements)
+{
+ unsigned int i;
+ int unipolar;
+
+ /* see if card is using a unipolar or bipolar range so we can munge data correctly */
+ unipolar = inb(dev->iobase + DAS1800_CONTROL_C) & UB;
+
+ /* convert to unsigned type if we are in a bipolar mode */
+ if (!unipolar) {
+ for (i = 0; i < num_elements; i++)
+ array[i] = munge_bipolar_sample(dev, array[i]);
+ }
+}
+
+static void das1800_handle_fifo_half_full(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das1800_private *devpriv = dev->private;
+ unsigned int nsamples = comedi_nsamples_left(s, FIFO_SIZE / 2);
+
+ insw(dev->iobase + DAS1800_FIFO, devpriv->fifo_buf, nsamples);
+ munge_data(dev, devpriv->fifo_buf, nsamples);
+ comedi_buf_write_samples(s, devpriv->fifo_buf, nsamples);
+}
+
+static void das1800_handle_fifo_not_empty(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned short dpnt;
+ int unipolar;
+
+ unipolar = inb(dev->iobase + DAS1800_CONTROL_C) & UB;
+
+ while (inb(dev->iobase + DAS1800_STATUS) & FNE) {
+ dpnt = inw(dev->iobase + DAS1800_FIFO);
+ /* convert to unsigned type */
+ dpnt = munge_bipolar_sample(dev, dpnt);
+ comedi_buf_write_samples(s, &dpnt, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ break;
+ }
+}
+
+/* Utility function used by das1800_flush_dma() and das1800_handle_dma() */
+static void das1800_flush_dma_channel(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_isadma_desc *desc)
+{
+ unsigned int residue = comedi_isadma_disable(desc->chan);
+ unsigned int nbytes = desc->size - residue;
+ unsigned int nsamples;
+
+ /* figure out how many points to read */
+ nsamples = comedi_bytes_to_samples(s, nbytes);
+ nsamples = comedi_nsamples_left(s, nsamples);
+
+ munge_data(dev, desc->virt_addr, nsamples);
+ comedi_buf_write_samples(s, desc->virt_addr, nsamples);
+}
+
+/* flushes remaining data from board when external trigger has stopped acquisition
+ * and we are using dma transfers */
+static void das1800_flush_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das1800_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL;
+
+ das1800_flush_dma_channel(dev, s, desc);
+
+ if (dual_dma) {
+ /* switch to other channel and flush it */
+ dma->cur_dma = 1 - dma->cur_dma;
+ desc = &dma->desc[dma->cur_dma];
+ das1800_flush_dma_channel(dev, s, desc);
+ }
+
+ /* get any remaining samples in fifo */
+ das1800_handle_fifo_not_empty(dev, s);
+}
+
+static void das1800_handle_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned int status)
+{
+ struct das1800_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL;
+
+ das1800_flush_dma_channel(dev, s, desc);
+
+ /* re-enable dma channel */
+ comedi_isadma_program(desc);
+
+ if (status & DMATC) {
+ /* clear DMATC interrupt bit */
+ outb(CLEAR_INTR_MASK & ~DMATC, dev->iobase + DAS1800_STATUS);
+ /* switch dma channels for next time, if appropriate */
+ if (dual_dma)
+ dma->cur_dma = 1 - dma->cur_dma;
+ }
+}
+
+static int das1800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct das1800_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc;
+ int i;
+
+ outb(0x0, dev->iobase + DAS1800_STATUS); /* disable conversions */
+ outb(0x0, dev->iobase + DAS1800_CONTROL_B); /* disable interrupts and dma */
+ outb(0x0, dev->iobase + DAS1800_CONTROL_A); /* disable and clear fifo and stop triggering */
+
+ for (i = 0; i < 2; i++) {
+ desc = &dma->desc[i];
+ if (desc->chan)
+ comedi_isadma_disable(desc->chan);
+ }
+
+ return 0;
+}
+
+/* the guts of the interrupt handler, that is shared with das1800_ai_poll */
+static void das1800_ai_handler(struct comedi_device *dev)
+{
+ struct das1800_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int status = inb(dev->iobase + DAS1800_STATUS);
+
+ /* select adc for base address + 0 */
+ outb(ADC, dev->iobase + DAS1800_SELECT);
+ /* dma buffer full */
+ if (devpriv->irq_dma_bits & DMA_ENABLED) {
+ /* look for data from dma transfer even if dma terminal count hasn't happened yet */
+ das1800_handle_dma(dev, s, status);
+ } else if (status & FHF) { /* if fifo half full */
+ das1800_handle_fifo_half_full(dev, s);
+ } else if (status & FNE) { /* if fifo not empty */
+ das1800_handle_fifo_not_empty(dev, s);
+ }
+
+ /* if the card's fifo has overflowed */
+ if (status & OVF) {
+ /* clear OVF interrupt bit */
+ outb(CLEAR_INTR_MASK & ~OVF, dev->iobase + DAS1800_STATUS);
+ dev_err(dev->class_dev, "FIFO overflow\n");
+ async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+ return;
+ }
+ /* stop taking data if appropriate */
+ /* stop_src TRIG_EXT */
+ if (status & CT0TC) {
+ /* clear CT0TC interrupt bit */
+ outb(CLEAR_INTR_MASK & ~CT0TC, dev->iobase + DAS1800_STATUS);
+ /* make sure we get all remaining data from board before quitting */
+ if (devpriv->irq_dma_bits & DMA_ENABLED)
+ das1800_flush_dma(dev, s);
+ else
+ das1800_handle_fifo_not_empty(dev, s);
+ async->events |= COMEDI_CB_EOA;
+ } else if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ comedi_handle_events(dev, s);
+}
+
+static int das1800_ai_poll(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned long flags;
+
+ /* prevent race with interrupt handler */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ das1800_ai_handler(dev);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return comedi_buf_n_bytes_ready(s);
+}
+
+static irqreturn_t das1800_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ unsigned int status;
+
+ if (!dev->attached) {
+ dev_err(dev->class_dev, "premature interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ /* Prevent race with das1800_ai_poll() on multi processor systems.
+ * Also protects indirect addressing in das1800_ai_handler */
+ spin_lock(&dev->spinlock);
+ status = inb(dev->iobase + DAS1800_STATUS);
+
+ /* if interrupt was not caused by das-1800 */
+ if (!(status & INT)) {
+ spin_unlock(&dev->spinlock);
+ return IRQ_NONE;
+ }
+ /* clear the interrupt status bit INT */
+ outb(CLEAR_INTR_MASK & ~INT, dev->iobase + DAS1800_STATUS);
+ /* handle interrupt */
+ das1800_ai_handler(dev);
+
+ spin_unlock(&dev->spinlock);
+ return IRQ_HANDLED;
+}
+
+/* converts requested conversion timing to timing compatible with
+ * hardware, used only when card is in 'burst mode'
+ */
+static unsigned int burst_convert_arg(unsigned int convert_arg, int flags)
+{
+ unsigned int micro_sec;
+
+ /* in burst mode, the maximum conversion time is 64 microseconds */
+ if (convert_arg > 64000)
+ convert_arg = 64000;
+
+ /* the conversion time must be an integral number of microseconds */
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ micro_sec = (convert_arg + 500) / 1000;
+ break;
+ case CMDF_ROUND_DOWN:
+ micro_sec = convert_arg / 1000;
+ break;
+ case CMDF_ROUND_UP:
+ micro_sec = (convert_arg - 1) / 1000 + 1;
+ break;
+ }
+
+ /* return number of nanoseconds */
+ return micro_sec * 1000;
+}
+
+static int das1800_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int unipolar0 = CR_RANGE(cmd->chanlist[0]) & UNIPOLAR;
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int unipolar = CR_RANGE(cmd->chanlist[i]) & UNIPOLAR;
+
+ if (unipolar != unipolar0) {
+ dev_dbg(dev->class_dev,
+ "unipolar and bipolar ranges cannot be mixed in the chanlist\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* test analog input cmd */
+static int das1800_ai_do_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct das1800_board *thisboard = dev->board_ptr;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_EXT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->scan_begin_src != TRIG_FOLLOW &&
+ cmd->convert_src != TRIG_TIMER)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ default:
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* we are not in burst mode */
+ arg = cmd->convert_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ } else if (cmd->convert_src == TRIG_TIMER) {
+ /* we are in burst mode */
+ arg = burst_convert_arg(cmd->convert_arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->chanlist_len;
+ err |= comedi_check_trigger_arg_max(&cmd->
+ scan_begin_arg,
+ arg);
+
+ arg = cmd->scan_begin_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg,
+ cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das1800_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+/* returns appropriate bits for control register a, depending on command */
+static int control_a_bits(const struct comedi_cmd *cmd)
+{
+ int control_a;
+
+ control_a = FFEN; /* enable fifo */
+ if (cmd->stop_src == TRIG_EXT)
+ control_a |= ATEN;
+ switch (cmd->start_src) {
+ case TRIG_EXT:
+ control_a |= TGEN | CGSL;
+ break;
+ case TRIG_NOW:
+ control_a |= CGEN;
+ break;
+ default:
+ break;
+ }
+
+ return control_a;
+}
+
+/* returns appropriate bits for control register c, depending on command */
+static int control_c_bits(const struct comedi_cmd *cmd)
+{
+ int control_c;
+ int aref;
+
+ /* set clock source to internal or external, select analog reference,
+ * select unipolar / bipolar
+ */
+ aref = CR_AREF(cmd->chanlist[0]);
+ control_c = UQEN; /* enable upper qram addresses */
+ if (aref != AREF_DIFF)
+ control_c |= SD;
+ if (aref == AREF_COMMON)
+ control_c |= CMEN;
+ /* if a unipolar range was selected */
+ if (CR_RANGE(cmd->chanlist[0]) & UNIPOLAR)
+ control_c |= UB;
+ switch (cmd->scan_begin_src) {
+ case TRIG_FOLLOW: /* not in burst mode */
+ switch (cmd->convert_src) {
+ case TRIG_TIMER:
+ /* trig on cascaded counters */
+ control_c |= IPCLK;
+ break;
+ case TRIG_EXT:
+ /* trig on falling edge of external trigger */
+ control_c |= XPCLK;
+ break;
+ default:
+ break;
+ }
+ break;
+ case TRIG_TIMER:
+ /* burst mode with internal pacer clock */
+ control_c |= BMDE | IPCLK;
+ break;
+ case TRIG_EXT:
+ /* burst mode with external trigger */
+ control_c |= BMDE | XPCLK;
+ break;
+ default:
+ break;
+ }
+
+ return control_c;
+}
+
+static unsigned int das1800_ai_transfer_size(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int maxbytes,
+ unsigned int ns)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int max_samples = comedi_bytes_to_samples(s, maxbytes);
+ unsigned int samples;
+
+ samples = max_samples;
+
+ /* for timed modes, make dma buffer fill in 'ns' time */
+ switch (cmd->scan_begin_src) {
+ case TRIG_FOLLOW: /* not in burst mode */
+ if (cmd->convert_src == TRIG_TIMER)
+ samples = ns / cmd->convert_arg;
+ break;
+ case TRIG_TIMER:
+ samples = ns / (cmd->scan_begin_arg * cmd->chanlist_len);
+ break;
+ }
+
+ /* limit samples to what is remaining in the command */
+ samples = comedi_nsamples_left(s, samples);
+
+ if (samples > max_samples)
+ samples = max_samples;
+ if (samples < 1)
+ samples = 1;
+
+ return comedi_samples_to_bytes(s, samples);
+}
+
+static void das1800_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das1800_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[0];
+ unsigned int bytes;
+
+ if ((devpriv->irq_dma_bits & DMA_ENABLED) == 0)
+ return;
+
+ dma->cur_dma = 0;
+
+ /* determine a dma transfer size to fill buffer in 0.3 sec */
+ bytes = das1800_ai_transfer_size(dev, s, desc->maxsize, 300000000);
+
+ desc->size = bytes;
+ comedi_isadma_program(desc);
+
+ /* set up dual dma if appropriate */
+ if (devpriv->irq_dma_bits & DMA_DUAL) {
+ desc = &dma->desc[1];
+ desc->size = bytes;
+ comedi_isadma_program(desc);
+ }
+}
+
+/* programs channel/gain list into card */
+static void program_chanlist(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ int i, n, chan_range;
+ unsigned long irq_flags;
+ const int range_mask = 0x3; /* masks unipolar/bipolar bit off range */
+ const int range_bitshift = 8;
+
+ n = cmd->chanlist_len;
+ /* spinlock protects indirect addressing */
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ outb(QRAM, dev->iobase + DAS1800_SELECT); /* select QRAM for baseAddress + 0x0 */
+ outb(n - 1, dev->iobase + DAS1800_QRAM_ADDRESS); /*set QRAM address start */
+ /* make channel / gain list */
+ for (i = 0; i < n; i++) {
+ chan_range =
+ CR_CHAN(cmd->chanlist[i]) |
+ ((CR_RANGE(cmd->chanlist[i]) & range_mask) <<
+ range_bitshift);
+ outw(chan_range, dev->iobase + DAS1800_QRAM);
+ }
+ outb(n - 1, dev->iobase + DAS1800_QRAM_ADDRESS); /*finish write to QRAM */
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+}
+
+/* analog input do_cmd */
+static int das1800_ai_do_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das1800_private *devpriv = dev->private;
+ int control_a, control_c;
+ struct comedi_async *async = s->async;
+ const struct comedi_cmd *cmd = &async->cmd;
+
+ /* disable dma on CMDF_WAKE_EOS, or CMDF_PRIORITY
+ * (because dma in handler is unsafe at hard real-time priority) */
+ if (cmd->flags & (CMDF_WAKE_EOS | CMDF_PRIORITY))
+ devpriv->irq_dma_bits &= ~DMA_ENABLED;
+ else
+ devpriv->irq_dma_bits |= devpriv->dma_bits;
+ /* interrupt on end of conversion for CMDF_WAKE_EOS */
+ if (cmd->flags & CMDF_WAKE_EOS) {
+ /* interrupt fifo not empty */
+ devpriv->irq_dma_bits &= ~FIMD;
+ } else {
+ /* interrupt fifo half full */
+ devpriv->irq_dma_bits |= FIMD;
+ }
+
+ das1800_cancel(dev, s);
+
+ /* determine proper bits for control registers */
+ control_a = control_a_bits(cmd);
+ control_c = control_c_bits(cmd);
+
+ /* setup card and start */
+ program_chanlist(dev, cmd);
+
+ /* setup cascaded counters for conversion/scan frequency */
+ if ((cmd->scan_begin_src == TRIG_FOLLOW ||
+ cmd->scan_begin_src == TRIG_TIMER) &&
+ cmd->convert_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ }
+
+ /* setup counter 0 for 'about triggering' */
+ if (cmd->stop_src == TRIG_EXT)
+ comedi_8254_load(dev->pacer, 0, 1, I8254_MODE0 | I8254_BINARY);
+
+ das1800_ai_setup_dma(dev, s);
+ outb(control_c, dev->iobase + DAS1800_CONTROL_C);
+ /* set conversion rate and length for burst mode */
+ if (control_c & BMDE) {
+ /* program conversion period with number of microseconds minus 1 */
+ outb(cmd->convert_arg / 1000 - 1,
+ dev->iobase + DAS1800_BURST_RATE);
+ outb(cmd->chanlist_len - 1, dev->iobase + DAS1800_BURST_LENGTH);
+ }
+ outb(devpriv->irq_dma_bits, dev->iobase + DAS1800_CONTROL_B); /* enable irq/dma */
+ outb(control_a, dev->iobase + DAS1800_CONTROL_A); /* enable fifo and triggering */
+ outb(CVEN, dev->iobase + DAS1800_STATUS); /* enable conversions */
+
+ return 0;
+}
+
+/* read analog input */
+static int das1800_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct das1800_board *thisboard = dev->board_ptr;
+ int i, n;
+ int chan, range, aref, chan_range;
+ int timeout = 1000;
+ unsigned short dpnt;
+ int conv_flags = 0;
+ unsigned long irq_flags;
+
+ /* set up analog reference and unipolar / bipolar mode */
+ aref = CR_AREF(insn->chanspec);
+ conv_flags |= UQEN;
+ if (aref != AREF_DIFF)
+ conv_flags |= SD;
+ if (aref == AREF_COMMON)
+ conv_flags |= CMEN;
+ /* if a unipolar range was selected */
+ if (CR_RANGE(insn->chanspec) & UNIPOLAR)
+ conv_flags |= UB;
+
+ outb(conv_flags, dev->iobase + DAS1800_CONTROL_C); /* software conversion enabled */
+ outb(CVEN, dev->iobase + DAS1800_STATUS); /* enable conversions */
+ outb(0x0, dev->iobase + DAS1800_CONTROL_A); /* reset fifo */
+ outb(FFEN, dev->iobase + DAS1800_CONTROL_A);
+
+ chan = CR_CHAN(insn->chanspec);
+ /* mask of unipolar/bipolar bit from range */
+ range = CR_RANGE(insn->chanspec) & 0x3;
+ chan_range = chan | (range << 8);
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ outb(QRAM, dev->iobase + DAS1800_SELECT); /* select QRAM for baseAddress + 0x0 */
+ outb(0x0, dev->iobase + DAS1800_QRAM_ADDRESS); /* set QRAM address start */
+ outw(chan_range, dev->iobase + DAS1800_QRAM);
+ outb(0x0, dev->iobase + DAS1800_QRAM_ADDRESS); /*finish write to QRAM */
+ outb(ADC, dev->iobase + DAS1800_SELECT); /* select ADC for baseAddress + 0x0 */
+
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion */
+ outb(0, dev->iobase + DAS1800_FIFO);
+ for (i = 0; i < timeout; i++) {
+ if (inb(dev->iobase + DAS1800_STATUS) & FNE)
+ break;
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev, "timeout\n");
+ n = -ETIME;
+ goto exit;
+ }
+ dpnt = inw(dev->iobase + DAS1800_FIFO);
+ /* shift data to offset binary for bipolar ranges */
+ if ((conv_flags & UB) == 0)
+ dpnt += 1 << (thisboard->resolution - 1);
+ data[n] = dpnt;
+ }
+exit:
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ return n;
+}
+
+/* writes to an analog output channel */
+static int das1800_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct das1800_board *thisboard = dev->board_ptr;
+ struct das1800_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+/* int range = CR_RANGE(insn->chanspec); */
+ int update_chan = thisboard->ao_n_chan - 1;
+ unsigned short output;
+ unsigned long irq_flags;
+
+ /* card expects two's complement data */
+ output = data[0] - (1 << (thisboard->resolution - 1));
+ /* if the write is to the 'update' channel, we need to remember its value */
+ if (chan == update_chan)
+ devpriv->ao_update_bits = output;
+ /* write to channel */
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ outb(DAC(chan), dev->iobase + DAS1800_SELECT); /* select dac channel for baseAddress + 0x0 */
+ outw(output, dev->iobase + DAS1800_DAC);
+ /* now we need to write to 'update' channel to update all dac channels */
+ if (chan != update_chan) {
+ outb(DAC(update_chan), dev->iobase + DAS1800_SELECT); /* select 'update' channel for baseAddress + 0x0 */
+ outw(devpriv->ao_update_bits, dev->iobase + DAS1800_DAC);
+ }
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ return 1;
+}
+
+/* reads from digital input channels */
+static int das1800_di_rbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = inb(dev->iobase + DAS1800_DIGITAL) & 0xf;
+ data[0] = 0;
+
+ return insn->n;
+}
+
+static int das1800_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS1800_DIGITAL);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void das1800_init_dma(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct das1800_private *devpriv = dev->private;
+ unsigned int *dma_chan;
+
+ /*
+ * it->options[2] is DMA channel 0
+ * it->options[3] is DMA channel 1
+ *
+ * Encode the DMA channels into 2 digit hexadecimal for switch.
+ */
+ dma_chan = &it->options[2];
+
+ switch ((dma_chan[0] & 0x7) | (dma_chan[1] << 4)) {
+ case 0x5: /* dma0 == 5 */
+ devpriv->dma_bits = DMA_CH5;
+ break;
+ case 0x6: /* dma0 == 6 */
+ devpriv->dma_bits = DMA_CH6;
+ break;
+ case 0x7: /* dma0 == 7 */
+ devpriv->dma_bits = DMA_CH7;
+ break;
+ case 0x65: /* dma0 == 5, dma1 == 6 */
+ devpriv->dma_bits = DMA_CH5_CH6;
+ break;
+ case 0x76: /* dma0 == 6, dma1 == 7 */
+ devpriv->dma_bits = DMA_CH6_CH7;
+ break;
+ case 0x57: /* dma0 == 7, dma1 == 5 */
+ devpriv->dma_bits = DMA_CH7_CH5;
+ break;
+ default:
+ return;
+ }
+
+ /* DMA can use 1 or 2 buffers, each with a separate channel */
+ devpriv->dma = comedi_isadma_alloc(dev, dma_chan[1] ? 2 : 1,
+ dma_chan[0], dma_chan[1],
+ DMA_BUF_SIZE, COMEDI_ISADMA_READ);
+ if (!devpriv->dma)
+ devpriv->dma_bits = 0;
+}
+
+static void das1800_free_dma(struct comedi_device *dev)
+{
+ struct das1800_private *devpriv = dev->private;
+
+ if (devpriv)
+ comedi_isadma_free(devpriv->dma);
+}
+
+static int das1800_probe(struct comedi_device *dev)
+{
+ const struct das1800_board *board = dev->board_ptr;
+ int index;
+ int id;
+
+ /* calc the offset to the boardinfo that was found by the core */
+ index = board - das1800_boards;
+
+ /* verify that the board id matches the boardinfo */
+ id = (inb(dev->iobase + DAS1800_DIGITAL) >> 4) & 0xf;
+ switch (id) {
+ case 0x3:
+ if (index == das1801st_da || index == das1802st_da ||
+ index == das1701st_da || index == das1702st_da)
+ return index;
+ index = das1801st;
+ break;
+ case 0x4:
+ if (index == das1802hr_da || index == das1702hr_da)
+ return index;
+ index = das1802hr;
+ break;
+ case 0x5:
+ if (index == das1801ao || index == das1802ao ||
+ index == das1701ao || index == das1702ao)
+ return index;
+ index = das1801ao;
+ break;
+ case 0x6:
+ if (index == das1802hr || index == das1702hr)
+ return index;
+ index = das1802hr;
+ break;
+ case 0x7:
+ if (index == das1801st || index == das1802st ||
+ index == das1701st || index == das1702st)
+ return index;
+ index = das1801st;
+ break;
+ case 0x8:
+ if (index == das1801hc || index == das1802hc)
+ return index;
+ index = das1801hc;
+ break;
+ default:
+ dev_err(dev->class_dev,
+ "Board model: probe returned 0x%x (unknown, please report)\n",
+ id);
+ break;
+ }
+ dev_err(dev->class_dev,
+ "Board model (probed, not recommended): %s series\n",
+ das1800_boards[index].name);
+
+ return index;
+}
+
+static int das1800_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct das1800_board *thisboard;
+ struct das1800_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int irq = it->options[1];
+ int board;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], DAS1800_SIZE);
+ if (ret)
+ return ret;
+
+ board = das1800_probe(dev);
+ if (board < 0) {
+ dev_err(dev->class_dev, "unable to determine board type\n");
+ return -ENODEV;
+ }
+
+ dev->board_ptr = das1800_boards + board;
+ thisboard = dev->board_ptr;
+ dev->board_name = thisboard->name;
+
+ /* if it is an 'ao' board with fancy analog out then we need extra io ports */
+ if (thisboard->ao_ability == 2) {
+ unsigned long iobase2 = dev->iobase + IOBASE2;
+
+ ret = __comedi_request_region(dev, iobase2, DAS1800_SIZE);
+ if (ret)
+ return ret;
+ devpriv->iobase2 = iobase2;
+ }
+
+ if (irq == 3 || irq == 5 || irq == 7 || irq == 10 || irq == 11 ||
+ irq == 15) {
+ ret = request_irq(irq, das1800_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = irq;
+
+ switch (irq) {
+ case 3:
+ devpriv->irq_dma_bits |= 0x8;
+ break;
+ case 5:
+ devpriv->irq_dma_bits |= 0x10;
+ break;
+ case 7:
+ devpriv->irq_dma_bits |= 0x18;
+ break;
+ case 10:
+ devpriv->irq_dma_bits |= 0x28;
+ break;
+ case 11:
+ devpriv->irq_dma_bits |= 0x30;
+ break;
+ case 15:
+ devpriv->irq_dma_bits |= 0x38;
+ break;
+ }
+ }
+ }
+
+ /* an irq and one dma channel is required to use dma */
+ if (dev->irq & it->options[2])
+ das1800_init_dma(dev, it);
+
+ devpriv->fifo_buf = kmalloc_array(FIFO_SIZE, sizeof(uint16_t), GFP_KERNEL);
+ if (!devpriv->fifo_buf)
+ return -ENOMEM;
+
+ dev->pacer = comedi_8254_init(dev->iobase + DAS1800_COUNTER,
+ I8254_OSC_BASE_5MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND;
+ if (thisboard->common)
+ s->subdev_flags |= SDF_COMMON;
+ s->n_chan = thisboard->qram_len;
+ s->maxdata = (1 << thisboard->resolution) - 1;
+ s->range_table = thisboard->range_ai;
+ s->insn_read = das1800_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmd = das1800_ai_do_cmd;
+ s->do_cmdtest = das1800_ai_do_cmdtest;
+ s->poll = das1800_ai_poll;
+ s->cancel = das1800_cancel;
+ }
+
+ /* analog out */
+ s = &dev->subdevices[1];
+ if (thisboard->ao_ability == 1) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = thisboard->ao_n_chan;
+ s->maxdata = (1 << thisboard->resolution) - 1;
+ s->range_table = &range_bipolar10;
+ s->insn_write = das1800_ao_winsn;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* di */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das1800_di_rbits;
+
+ /* do */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = thisboard->do_n_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das1800_do_wbits;
+
+ das1800_cancel(dev, dev->read_subdev);
+
+ /* initialize digital out channels */
+ outb(0, dev->iobase + DAS1800_DIGITAL);
+
+ /* initialize analog out channels */
+ if (thisboard->ao_ability == 1) {
+ /* select 'update' dac channel for baseAddress + 0x0 */
+ outb(DAC(thisboard->ao_n_chan - 1),
+ dev->iobase + DAS1800_SELECT);
+ outw(devpriv->ao_update_bits, dev->iobase + DAS1800_DAC);
+ }
+
+ return 0;
+};
+
+static void das1800_detach(struct comedi_device *dev)
+{
+ struct das1800_private *devpriv = dev->private;
+
+ das1800_free_dma(dev);
+ if (devpriv) {
+ kfree(devpriv->fifo_buf);
+ if (devpriv->iobase2)
+ release_region(devpriv->iobase2, DAS1800_SIZE);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver das1800_driver = {
+ .driver_name = "das1800",
+ .module = THIS_MODULE,
+ .attach = das1800_attach,
+ .detach = das1800_detach,
+ .num_names = ARRAY_SIZE(das1800_boards),
+ .board_name = &das1800_boards[0].name,
+ .offset = sizeof(struct das1800_board),
+};
+module_comedi_driver(das1800_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das6402.c b/drivers/staging/comedi/drivers/das6402.c
new file mode 100644
index 000000000..1701294b7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das6402.c
@@ -0,0 +1,676 @@
+/*
+ * das6402.c
+ * Comedi driver for DAS6402 compatible boards
+ * Copyright(c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Rewrite of an experimental driver by:
+ * Copyright (C) 1999 Oystein Svendsen <svendsen@pvv.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: das6402
+ * Description: Keithley Metrabyte DAS6402 (& compatibles)
+ * Devices: [Keithley Metrabyte] DAS6402-12 (das6402-12),
+ * DAS6402-16 (das6402-16)
+ * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Updated: Fri, 14 Mar 2014 10:18:43 -0700
+ * Status: unknown
+ *
+ * Configuration Options:
+ * [0] - I/O base address
+ * [1] - IRQ (optional, needed for async command support)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_8254.h"
+
+/*
+ * Register I/O map
+ */
+#define DAS6402_AI_DATA_REG 0x00
+#define DAS6402_AI_MUX_REG 0x02
+#define DAS6402_AI_MUX_LO(x) (((x) & 0x3f) << 0)
+#define DAS6402_AI_MUX_HI(x) (((x) & 0x3f) << 8)
+#define DAS6402_DI_DO_REG 0x03
+#define DAS6402_AO_DATA_REG(x) (0x04 + ((x) * 2))
+#define DAS6402_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define DAS6402_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define DAS6402_STATUS_REG 0x08
+#define DAS6402_STATUS_FFNE (1 << 0)
+#define DAS6402_STATUS_FHALF (1 << 1)
+#define DAS6402_STATUS_FFULL (1 << 2)
+#define DAS6402_STATUS_XINT (1 << 3)
+#define DAS6402_STATUS_INT (1 << 4)
+#define DAS6402_STATUS_XTRIG (1 << 5)
+#define DAS6402_STATUS_INDGT (1 << 6)
+#define DAS6402_STATUS_10MHZ (1 << 7)
+#define DAS6402_STATUS_W_CLRINT (1 << 0)
+#define DAS6402_STATUS_W_CLRXTR (1 << 1)
+#define DAS6402_STATUS_W_CLRXIN (1 << 2)
+#define DAS6402_STATUS_W_EXTEND (1 << 4)
+#define DAS6402_STATUS_W_ARMED (1 << 5)
+#define DAS6402_STATUS_W_POSTMODE (1 << 6)
+#define DAS6402_STATUS_W_10MHZ (1 << 7)
+#define DAS6402_CTRL_REG 0x09
+#define DAS6402_CTRL_SOFT_TRIG (0 << 0)
+#define DAS6402_CTRL_EXT_FALL_TRIG (1 << 0)
+#define DAS6402_CTRL_EXT_RISE_TRIG (2 << 0)
+#define DAS6402_CTRL_PACER_TRIG (3 << 0)
+#define DAS6402_CTRL_BURSTEN (1 << 2)
+#define DAS6402_CTRL_XINTE (1 << 3)
+#define DAS6402_CTRL_IRQ(x) ((x) << 4)
+#define DAS6402_CTRL_INTE (1 << 7)
+#define DAS6402_TRIG_REG 0x0a
+#define DAS6402_TRIG_TGEN (1 << 0)
+#define DAS6402_TRIG_TGSEL (1 << 1)
+#define DAS6402_TRIG_TGPOL (1 << 2)
+#define DAS6402_TRIG_PRETRIG (1 << 3)
+#define DAS6402_AO_RANGE(_chan, _range) ((_range) << ((_chan) ? 6 : 4))
+#define DAS6402_AO_RANGE_MASK(_chan) (3 << ((_chan) ? 6 : 4))
+#define DAS6402_MODE_REG 0x0b
+#define DAS6402_MODE_RANGE(x) ((x) << 0)
+#define DAS6402_MODE_POLLED (0 << 2)
+#define DAS6402_MODE_FIFONEPTY (1 << 2)
+#define DAS6402_MODE_FIFOHFULL (2 << 2)
+#define DAS6402_MODE_EOB (3 << 2)
+#define DAS6402_MODE_ENHANCED (1 << 4)
+#define DAS6402_MODE_SE (1 << 5)
+#define DAS6402_MODE_UNI (1 << 6)
+#define DAS6402_MODE_DMA1 (0 << 7)
+#define DAS6402_MODE_DMA3 (1 << 7)
+#define DAS6402_TIMER_BASE 0x0c
+
+static const struct comedi_lrange das6402_ai_ranges = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+/*
+ * Analog output ranges are programmable on the DAS6402/12.
+ * For the DAS6402/16 the range bits have no function, the
+ * DAC ranges are selected by switches on the board.
+ */
+static const struct comedi_lrange das6402_ao_ranges = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+struct das6402_boardinfo {
+ const char *name;
+ unsigned int maxdata;
+};
+
+static struct das6402_boardinfo das6402_boards[] = {
+ {
+ .name = "das6402-12",
+ .maxdata = 0x0fff,
+ }, {
+ .name = "das6402-16",
+ .maxdata = 0xffff,
+ },
+};
+
+struct das6402_private {
+ unsigned int irq;
+ unsigned int ao_range;
+};
+
+static void das6402_set_mode(struct comedi_device *dev,
+ unsigned int mode)
+{
+ outb(DAS6402_MODE_ENHANCED | mode, dev->iobase + DAS6402_MODE_REG);
+}
+
+static void das6402_set_extended(struct comedi_device *dev,
+ unsigned int val)
+{
+ outb(DAS6402_STATUS_W_EXTEND, dev->iobase + DAS6402_STATUS_REG);
+ outb(DAS6402_STATUS_W_EXTEND | val, dev->iobase + DAS6402_STATUS_REG);
+ outb(val, dev->iobase + DAS6402_STATUS_REG);
+}
+
+static void das6402_clear_all_interrupts(struct comedi_device *dev)
+{
+ outb(DAS6402_STATUS_W_CLRINT |
+ DAS6402_STATUS_W_CLRXTR |
+ DAS6402_STATUS_W_CLRXIN, dev->iobase + DAS6402_STATUS_REG);
+}
+
+static void das6402_ai_clear_eoc(struct comedi_device *dev)
+{
+ outb(DAS6402_STATUS_W_CLRINT, dev->iobase + DAS6402_STATUS_REG);
+}
+
+static unsigned int das6402_ai_read_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inw(dev->iobase + DAS6402_AI_DATA_REG);
+ if (s->maxdata == 0x0fff)
+ val >>= 4;
+ return val;
+}
+
+static irqreturn_t das6402_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS6402_STATUS_REG);
+ if ((status & DAS6402_STATUS_INT) == 0)
+ return IRQ_NONE;
+
+ if (status & DAS6402_STATUS_FFULL) {
+ async->events |= COMEDI_CB_OVERFLOW;
+ } else if (status & DAS6402_STATUS_FFNE) {
+ unsigned int val;
+
+ val = das6402_ai_read_sample(dev, s);
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ das6402_clear_all_interrupts(dev);
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static void das6402_ai_set_mode(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec,
+ unsigned int mode)
+{
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+
+ mode |= DAS6402_MODE_RANGE(range);
+ if (aref == AREF_GROUND)
+ mode |= DAS6402_MODE_SE;
+ if (comedi_range_is_unipolar(s, range))
+ mode |= DAS6402_MODE_UNI;
+
+ das6402_set_mode(dev, mode);
+}
+
+static int das6402_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das6402_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int chan_lo = CR_CHAN(cmd->chanlist[0]);
+ unsigned int chan_hi = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]);
+
+ das6402_ai_set_mode(dev, s, cmd->chanlist[0], DAS6402_MODE_FIFONEPTY);
+
+ /* load the mux for chanlist conversion */
+ outw(DAS6402_AI_MUX_HI(chan_hi) | DAS6402_AI_MUX_LO(chan_lo),
+ dev->iobase + DAS6402_AI_MUX_REG);
+
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+
+ /* enable interrupt and pacer trigger */
+ outb(DAS6402_CTRL_INTE |
+ DAS6402_CTRL_IRQ(devpriv->irq) |
+ DAS6402_CTRL_PACER_TRIG, dev->iobase + DAS6402_CTRL_REG);
+
+ return 0;
+}
+
+static int das6402_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (chan != chan0 + i) {
+ dev_dbg(dev->class_dev,
+ "chanlist must be consecutive\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "chanlist must have the same range\n");
+ return -EINVAL;
+ }
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "chanlist must have the same reference\n");
+ return -EINVAL;
+ }
+
+ if (aref0 == AREF_DIFF && chan > (s->n_chan / 2)) {
+ dev_dbg(dev->class_dev,
+ "chanlist differential channel to large\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int das6402_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->convert_arg;
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das6402_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int das6402_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG);
+
+ return 0;
+}
+
+static void das6402_ai_soft_trig(struct comedi_device *dev)
+{
+ outw(0, dev->iobase + DAS6402_AI_DATA_REG);
+}
+
+static int das6402_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS6402_STATUS_REG);
+ if (status & DAS6402_STATUS_FFNE)
+ return 0;
+ return -EBUSY;
+}
+
+static int das6402_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ int ret;
+ int i;
+
+ if (aref == AREF_DIFF && chan > (s->n_chan / 2))
+ return -EINVAL;
+
+ /* enable software conversion trigger */
+ outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG);
+
+ das6402_ai_set_mode(dev, s, insn->chanspec, DAS6402_MODE_POLLED);
+
+ /* load the mux for single channel conversion */
+ outw(DAS6402_AI_MUX_HI(chan) | DAS6402_AI_MUX_LO(chan),
+ dev->iobase + DAS6402_AI_MUX_REG);
+
+ for (i = 0; i < insn->n; i++) {
+ das6402_ai_clear_eoc(dev);
+ das6402_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, das6402_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[i] = das6402_ai_read_sample(dev, s);
+ }
+
+ das6402_ai_clear_eoc(dev);
+
+ return insn->n;
+}
+
+static int das6402_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das6402_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ /* set the range for this channel */
+ val = devpriv->ao_range;
+ val &= ~DAS6402_AO_RANGE_MASK(chan);
+ val |= DAS6402_AO_RANGE(chan, range);
+ if (val != devpriv->ao_range) {
+ devpriv->ao_range = val;
+ outb(val, dev->iobase + DAS6402_TRIG_REG);
+ }
+
+ /*
+ * The DAS6402/16 has a jumper to select either individual
+ * update (UPDATE) or simultaneous updating (XFER) of both
+ * DAC's. In UPDATE mode, when the MSB is written, that DAC
+ * is updated. In XFER mode, after both DAC's are loaded,
+ * a read cycle of any DAC register will update both DAC's
+ * simultaneously.
+ *
+ * If you have XFER mode enabled a (*insn_read) will need
+ * to be performed in order to update the DAC's with the
+ * last value written.
+ */
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ s->readback[chan] = val;
+
+ if (s->maxdata == 0x0fff) {
+ /*
+ * DAS6402/12 has the two 8-bit DAC registers, left
+ * justified (the 4 LSB bits are don't care). Data
+ * can be written as one word.
+ */
+ val <<= 4;
+ outw(val, dev->iobase + DAS6402_AO_DATA_REG(chan));
+ } else {
+ /*
+ * DAS6402/16 uses both 8-bit DAC registers and needs
+ * to be written LSB then MSB.
+ */
+ outb(val & 0xff,
+ dev->iobase + DAS6402_AO_LSB_REG(chan));
+ outb((val >> 8) & 0xff,
+ dev->iobase + DAS6402_AO_LSB_REG(chan));
+ }
+ }
+
+ return insn->n;
+}
+
+static int das6402_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * If XFER mode is enabled, reading any DAC register
+ * will update both DAC's simultaneously.
+ */
+ inw(dev->iobase + DAS6402_AO_LSB_REG(chan));
+
+ return comedi_readback_insn_read(dev, s, insn, data);
+}
+
+static int das6402_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + DAS6402_DI_DO_REG);
+
+ return insn->n;
+}
+
+static int das6402_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS6402_DI_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void das6402_reset(struct comedi_device *dev)
+{
+ struct das6402_private *devpriv = dev->private;
+
+ /* enable "Enhanced" mode */
+ outb(DAS6402_MODE_ENHANCED, dev->iobase + DAS6402_MODE_REG);
+
+ /* enable 10MHz pacer clock */
+ das6402_set_extended(dev, DAS6402_STATUS_W_10MHZ);
+
+ /* enable software conversion trigger */
+ outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG);
+
+ /* default ADC to single-ended unipolar 10V inputs */
+ das6402_set_mode(dev, DAS6402_MODE_RANGE(0) |
+ DAS6402_MODE_POLLED |
+ DAS6402_MODE_SE |
+ DAS6402_MODE_UNI);
+
+ /* default mux for single channel conversion (channel 0) */
+ outw(DAS6402_AI_MUX_HI(0) | DAS6402_AI_MUX_LO(0),
+ dev->iobase + DAS6402_AI_MUX_REG);
+
+ /* set both DAC's for unipolar 5V output range */
+ devpriv->ao_range = DAS6402_AO_RANGE(0, 2) | DAS6402_AO_RANGE(1, 2);
+ outb(devpriv->ao_range, dev->iobase + DAS6402_TRIG_REG);
+
+ /* set both DAC's to 0V */
+ outw(0, dev->iobase + DAS6402_AO_DATA_REG(0));
+ outw(0, dev->iobase + DAS6402_AO_DATA_REG(0));
+ inw(dev->iobase + DAS6402_AO_LSB_REG(0));
+
+ /* set all digital outputs low */
+ outb(0, dev->iobase + DAS6402_DI_DO_REG);
+
+ das6402_clear_all_interrupts(dev);
+}
+
+static int das6402_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct das6402_boardinfo *board = dev->board_ptr;
+ struct das6402_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ das6402_reset(dev);
+
+ /* IRQs 2,3,5,6,7, 10,11,15 are valid for "enhanced" mode */
+ if ((1 << it->options[1]) & 0x8cec) {
+ ret = request_irq(it->options[1], das6402_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = it->options[1];
+
+ switch (dev->irq) {
+ case 10:
+ devpriv->irq = 4;
+ break;
+ case 11:
+ devpriv->irq = 1;
+ break;
+ case 15:
+ devpriv->irq = 6;
+ break;
+ default:
+ devpriv->irq = dev->irq;
+ break;
+ }
+ }
+ }
+
+ dev->pacer = comedi_8254_init(dev->iobase + DAS6402_TIMER_BASE,
+ I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 64;
+ s->maxdata = board->maxdata;
+ s->range_table = &das6402_ai_ranges;
+ s->insn_read = das6402_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = das6402_ai_cmdtest;
+ s->do_cmd = das6402_ai_cmd;
+ s->cancel = das6402_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = board->maxdata;
+ s->range_table = &das6402_ao_ranges;
+ s->insn_write = das6402_ao_insn_write;
+ s->insn_read = das6402_ao_insn_read;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das6402_di_insn_bits;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das6402_do_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver das6402_driver = {
+ .driver_name = "das6402",
+ .module = THIS_MODULE,
+ .attach = das6402_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &das6402_boards[0].name,
+ .num_names = ARRAY_SIZE(das6402_boards),
+ .offset = sizeof(struct das6402_boardinfo),
+};
+module_comedi_driver(das6402_driver)
+
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("Comedi driver for DAS6402 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das800.c b/drivers/staging/comedi/drivers/das800.c
new file mode 100644
index 000000000..39d304a12
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das800.c
@@ -0,0 +1,748 @@
+/*
+ comedi/drivers/das800.c
+ Driver for Keitley das800 series boards and compatibles
+ Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: das800
+Description: Keithley Metrabyte DAS800 (& compatibles)
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+Devices: [Keithley Metrabyte] DAS-800 (das-800), DAS-801 (das-801),
+ DAS-802 (das-802),
+ [Measurement Computing] CIO-DAS800 (cio-das800),
+ CIO-DAS801 (cio-das801), CIO-DAS802 (cio-das802),
+ CIO-DAS802/16 (cio-das802/16)
+Status: works, cio-das802/16 untested - email me if you have tested it
+
+Configuration options:
+ [0] - I/O port base address
+ [1] - IRQ (optional, required for timed or externally triggered conversions)
+
+Notes:
+ IRQ can be omitted, although the cmd interface will not work without it.
+
+ All entries in the channel/gain list must use the same gain and be
+ consecutive channels counting upwards in channel number (these are
+ hardware limitations.)
+
+ I've never tested the gain setting stuff since I only have a
+ DAS-800 board with fixed gain.
+
+ The cio-das802/16 does not have a fifo-empty status bit! Therefore
+ only fifo-half-full transfers are possible with this card.
+
+cmd triggers supported:
+ start_src: TRIG_NOW | TRIG_EXT
+ scan_begin_src: TRIG_FOLLOW
+ scan_end_src: TRIG_COUNT
+ convert_src: TRIG_TIMER | TRIG_EXT
+ stop_src: TRIG_NONE | TRIG_COUNT
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include "../comedidev.h"
+
+#include "comedi_8254.h"
+
+#define N_CHAN_AI 8 /* number of analog input channels */
+
+/* Registers for the das800 */
+
+#define DAS800_LSB 0
+#define FIFO_EMPTY 0x1
+#define FIFO_OVF 0x2
+#define DAS800_MSB 1
+#define DAS800_CONTROL1 2
+#define CONTROL1_INTE 0x8
+#define DAS800_CONV_CONTROL 2
+#define ITE 0x1
+#define CASC 0x2
+#define DTEN 0x4
+#define IEOC 0x8
+#define EACS 0x10
+#define CONV_HCEN 0x80
+#define DAS800_SCAN_LIMITS 2
+#define DAS800_STATUS 2
+#define IRQ 0x8
+#define BUSY 0x80
+#define DAS800_GAIN 3
+#define CIO_FFOV 0x8 /* cio-das802/16 fifo overflow */
+#define CIO_ENHF 0x90 /* cio-das802/16 fifo half full int ena */
+#define CONTROL1 0x80
+#define CONV_CONTROL 0xa0
+#define SCAN_LIMITS 0xc0
+#define ID 0xe0
+#define DAS800_8254 4
+#define DAS800_STATUS2 7
+#define STATUS2_HCEN 0x80
+#define STATUS2_INTE 0X20
+#define DAS800_ID 7
+
+#define DAS802_16_HALF_FIFO_SZ 128
+
+struct das800_board {
+ const char *name;
+ int ai_speed;
+ const struct comedi_lrange *ai_range;
+ int resolution;
+};
+
+static const struct comedi_lrange range_das801_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(0.5),
+ UNI_RANGE(1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(0.1),
+ BIP_RANGE(0.01),
+ UNI_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_cio_das801_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(0.5),
+ UNI_RANGE(1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(0.1),
+ BIP_RANGE(0.005),
+ UNI_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_das802_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(2.5),
+ UNI_RANGE(5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(2.5),
+ BIP_RANGE(0.625),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_das80216_ai = {
+ 8, {
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ UNI_RANGE(5),
+ BIP_RANGE(2.5),
+ UNI_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(1.25)
+ }
+};
+
+enum das800_boardinfo {
+ BOARD_DAS800,
+ BOARD_CIODAS800,
+ BOARD_DAS801,
+ BOARD_CIODAS801,
+ BOARD_DAS802,
+ BOARD_CIODAS802,
+ BOARD_CIODAS80216,
+};
+
+static const struct das800_board das800_boards[] = {
+ [BOARD_DAS800] = {
+ .name = "das-800",
+ .ai_speed = 25000,
+ .ai_range = &range_bipolar5,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS800] = {
+ .name = "cio-das800",
+ .ai_speed = 20000,
+ .ai_range = &range_bipolar5,
+ .resolution = 12,
+ },
+ [BOARD_DAS801] = {
+ .name = "das-801",
+ .ai_speed = 25000,
+ .ai_range = &range_das801_ai,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS801] = {
+ .name = "cio-das801",
+ .ai_speed = 20000,
+ .ai_range = &range_cio_das801_ai,
+ .resolution = 12,
+ },
+ [BOARD_DAS802] = {
+ .name = "das-802",
+ .ai_speed = 25000,
+ .ai_range = &range_das802_ai,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS802] = {
+ .name = "cio-das802",
+ .ai_speed = 20000,
+ .ai_range = &range_das802_ai,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS80216] = {
+ .name = "cio-das802/16",
+ .ai_speed = 10000,
+ .ai_range = &range_das80216_ai,
+ .resolution = 16,
+ },
+};
+
+struct das800_private {
+ unsigned int do_bits; /* digital output bits */
+};
+
+static void das800_ind_write(struct comedi_device *dev,
+ unsigned val, unsigned reg)
+{
+ /*
+ * Select dev->iobase + 2 to be desired register
+ * then write to that register.
+ */
+ outb(reg, dev->iobase + DAS800_GAIN);
+ outb(val, dev->iobase + 2);
+}
+
+static unsigned das800_ind_read(struct comedi_device *dev, unsigned reg)
+{
+ /*
+ * Select dev->iobase + 7 to be desired register
+ * then read from that register.
+ */
+ outb(reg, dev->iobase + DAS800_GAIN);
+ return inb(dev->iobase + 7);
+}
+
+static void das800_enable(struct comedi_device *dev)
+{
+ const struct das800_board *thisboard = dev->board_ptr;
+ struct das800_private *devpriv = dev->private;
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ /* enable fifo-half full interrupts for cio-das802/16 */
+ if (thisboard->resolution == 16)
+ outb(CIO_ENHF, dev->iobase + DAS800_GAIN);
+ /* enable hardware triggering */
+ das800_ind_write(dev, CONV_HCEN, CONV_CONTROL);
+ /* enable card's interrupt */
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+}
+
+static void das800_disable(struct comedi_device *dev)
+{
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ /* disable hardware triggering of conversions */
+ das800_ind_write(dev, 0x0, CONV_CONTROL);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+}
+
+static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ das800_disable(dev);
+ return 0;
+}
+
+static int das800_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i) % s->n_chan) {
+ dev_dbg(dev->class_dev,
+ "chanlist must be consecutive, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int das800_ai_do_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct das800_board *thisboard = dev->board_ptr;
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ unsigned int arg = cmd->convert_arg;
+
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das800_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int das800_ai_do_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct das800_board *thisboard = dev->board_ptr;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int gain = CR_RANGE(cmd->chanlist[0]);
+ unsigned int start_chan = CR_CHAN(cmd->chanlist[0]);
+ unsigned int end_chan = (start_chan + cmd->chanlist_len - 1) % 8;
+ unsigned int scan_chans = (end_chan << 3) | start_chan;
+ int conv_bits;
+ unsigned long irq_flags;
+
+ das800_disable(dev);
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ /* set scan limits */
+ das800_ind_write(dev, scan_chans, SCAN_LIMITS);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ /* set gain */
+ if (thisboard->resolution == 12 && gain > 0)
+ gain += 0x7;
+ gain &= 0xf;
+ outb(gain, dev->iobase + DAS800_GAIN);
+
+ /* enable auto channel scan, send interrupts on end of conversion
+ * and set clock source to internal or external
+ */
+ conv_bits = 0;
+ conv_bits |= EACS | IEOC;
+ if (cmd->start_src == TRIG_EXT)
+ conv_bits |= DTEN;
+ if (cmd->convert_src == TRIG_TIMER) {
+ conv_bits |= CASC | ITE;
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ }
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, conv_bits, CONV_CONTROL);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ das800_enable(dev);
+ return 0;
+}
+
+static unsigned int das800_ai_get_sample(struct comedi_device *dev)
+{
+ unsigned int lsb = inb(dev->iobase + DAS800_LSB);
+ unsigned int msb = inb(dev->iobase + DAS800_MSB);
+
+ return (msb << 8) | lsb;
+}
+
+static irqreturn_t das800_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct das800_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ unsigned long irq_flags;
+ unsigned int status;
+ unsigned int val;
+ bool fifo_empty;
+ bool fifo_overflow;
+ int i;
+
+ status = inb(dev->iobase + DAS800_STATUS);
+ if (!(status & IRQ))
+ return IRQ_NONE;
+ if (!dev->attached)
+ return IRQ_HANDLED;
+
+ async = s->async;
+ cmd = &async->cmd;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ status = das800_ind_read(dev, CONTROL1) & STATUS2_HCEN;
+ /*
+ * Don't release spinlock yet since we want to make sure
+ * no one else disables hardware conversions.
+ */
+
+ /* if hardware conversions are not enabled, then quit */
+ if (status == 0) {
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < DAS802_16_HALF_FIFO_SZ; i++) {
+ val = das800_ai_get_sample(dev);
+ if (s->maxdata == 0x0fff) {
+ fifo_empty = !!(val & FIFO_EMPTY);
+ fifo_overflow = !!(val & FIFO_OVF);
+ } else {
+ /* cio-das802/16 has no fifo empty status bit */
+ fifo_empty = false;
+ fifo_overflow = !!(inb(dev->iobase + DAS800_GAIN) &
+ CIO_FFOV);
+ }
+ if (fifo_empty || fifo_overflow)
+ break;
+
+ if (s->maxdata == 0x0fff)
+ val >>= 4; /* 12-bit sample */
+
+ val &= s->maxdata;
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+
+ if (fifo_overflow) {
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+ }
+
+ if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
+ /*
+ * Re-enable card's interrupt.
+ * We already have spinlock, so indirect addressing is safe
+ */
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits,
+ CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ } else {
+ /* otherwise, stop taking data */
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ das800_disable(dev);
+ }
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int das800_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS800_STATUS);
+ if ((status & BUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int das800_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das800_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned long irq_flags;
+ unsigned int val;
+ int ret;
+ int i;
+
+ das800_disable(dev);
+
+ /* set multiplexer */
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, chan | devpriv->do_bits, CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ /* set gain / range */
+ if (s->maxdata == 0x0fff && range)
+ range += 0x7;
+ range &= 0xf;
+ outb(range, dev->iobase + DAS800_GAIN);
+
+ udelay(5);
+
+ for (i = 0; i < insn->n; i++) {
+ /* trigger conversion */
+ outb_p(0, dev->iobase + DAS800_MSB);
+
+ ret = comedi_timeout(dev, s, insn, das800_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = das800_ai_get_sample(dev);
+ if (s->maxdata == 0x0fff)
+ val >>= 4; /* 12-bit sample */
+ data[i] = val & s->maxdata;
+ }
+
+ return insn->n;
+}
+
+static int das800_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = (inb(dev->iobase + DAS800_STATUS) >> 4) & 0x7;
+
+ return insn->n;
+}
+
+static int das800_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das800_private *devpriv = dev->private;
+ unsigned long irq_flags;
+
+ if (comedi_dio_update_state(s, data)) {
+ devpriv->do_bits = s->state << 4;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits,
+ CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int das800_probe(struct comedi_device *dev)
+{
+ const struct das800_board *thisboard = dev->board_ptr;
+ int board = thisboard ? thisboard - das800_boards : -EINVAL;
+ int id_bits;
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ id_bits = das800_ind_read(dev, ID) & 0x3;
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ switch (id_bits) {
+ case 0x0:
+ if (board == BOARD_DAS800 || board == BOARD_CIODAS800)
+ break;
+ dev_dbg(dev->class_dev, "Board model (probed): DAS-800\n");
+ board = BOARD_DAS800;
+ break;
+ case 0x2:
+ if (board == BOARD_DAS801 || board == BOARD_CIODAS801)
+ break;
+ dev_dbg(dev->class_dev, "Board model (probed): DAS-801\n");
+ board = BOARD_DAS801;
+ break;
+ case 0x3:
+ if (board == BOARD_DAS802 || board == BOARD_CIODAS802 ||
+ board == BOARD_CIODAS80216)
+ break;
+ dev_dbg(dev->class_dev, "Board model (probed): DAS-802\n");
+ board = BOARD_DAS802;
+ break;
+ default:
+ dev_dbg(dev->class_dev, "Board model: 0x%x (unknown)\n",
+ id_bits);
+ board = -EINVAL;
+ break;
+ }
+ return board;
+}
+
+static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct das800_board *thisboard;
+ struct das800_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int irq = it->options[1];
+ unsigned long irq_flags;
+ int board;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x8);
+ if (ret)
+ return ret;
+
+ board = das800_probe(dev);
+ if (board < 0) {
+ dev_dbg(dev->class_dev, "unable to determine board type\n");
+ return -ENODEV;
+ }
+ dev->board_ptr = das800_boards + board;
+ thisboard = dev->board_ptr;
+ dev->board_name = thisboard->name;
+
+ if (irq > 1 && irq <= 7) {
+ ret = request_irq(irq, das800_interrupt, 0, dev->board_name,
+ dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ dev->pacer = comedi_8254_init(dev->iobase + DAS800_8254,
+ I8254_OSC_BASE_1MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->maxdata = (1 << thisboard->resolution) - 1;
+ s->range_table = thisboard->ai_range;
+ s->insn_read = das800_ai_insn_read;
+ if (dev->irq) {
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 8;
+ s->do_cmdtest = das800_ai_do_cmdtest;
+ s->do_cmd = das800_ai_do_cmd;
+ s->cancel = das800_cancel;
+ }
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 3;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das800_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das800_do_insn_bits;
+
+ das800_disable(dev);
+
+ /* initialize digital out channels */
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ return 0;
+};
+
+static struct comedi_driver driver_das800 = {
+ .driver_name = "das800",
+ .module = THIS_MODULE,
+ .attach = das800_attach,
+ .detach = comedi_legacy_detach,
+ .num_names = ARRAY_SIZE(das800_boards),
+ .board_name = &das800_boards[0].name,
+ .offset = sizeof(struct das800_board),
+};
+module_comedi_driver(driver_das800);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dmm32at.c b/drivers/staging/comedi/drivers/dmm32at.c
new file mode 100644
index 000000000..bb2883c83
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dmm32at.c
@@ -0,0 +1,627 @@
+/*
+ * dmm32at.c
+ * Diamond Systems Diamond-MM-32-AT Comedi driver
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: dmm32at
+ * Description: Diamond Systems Diamond-MM-32-AT
+ * Devices: [Diamond Systems] Diamond-MM-32-AT (dmm32at)
+ * Author: Perry J. Piplani <perry.j.piplani@nasa.gov>
+ * Updated: Fri Jun 4 09:13:24 CDT 2004
+ * Status: experimental
+ *
+ * Configuration Options:
+ * comedi_config /dev/comedi0 dmm32at baseaddr,irq
+ *
+ * This driver is for the Diamond Systems MM-32-AT board
+ * http://www.diamondsystems.com/products/diamondmm32at
+ *
+ * It is being used on several projects inside NASA, without
+ * problems so far. For analog input commands, TRIG_EXT is not
+ * yet supported.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+
+/* Board register addresses */
+#define DMM32AT_AI_START_CONV_REG 0x00
+#define DMM32AT_AI_LSB_REG 0x00
+#define DMM32AT_AUX_DOUT_REG 0x01
+#define DMM32AT_AUX_DOUT2 (1 << 2) /* J3.42 - OUT2 (OUT2EN) */
+#define DMM32AT_AUX_DOUT1 (1 << 1) /* J3.43 */
+#define DMM32AT_AUX_DOUT0 (1 << 0) /* J3.44 - OUT0 (OUT0EN) */
+#define DMM32AT_AI_MSB_REG 0x01
+#define DMM32AT_AI_LO_CHAN_REG 0x02
+#define DMM32AT_AI_HI_CHAN_REG 0x03
+#define DMM32AT_AUX_DI_REG 0x04
+#define DMM32AT_AUX_DI_DACBUSY (1 << 7)
+#define DMM32AT_AUX_DI_CALBUSY (1 << 6)
+#define DMM32AT_AUX_DI3 (1 << 3) /* J3.45 - ADCLK (CLKSEL) */
+#define DMM32AT_AUX_DI2 (1 << 2) /* J3.46 - GATE12 (GT12EN) */
+#define DMM32AT_AUX_DI1 (1 << 1) /* J3.47 - GATE0 (GT0EN) */
+#define DMM32AT_AUX_DI0 (1 << 0) /* J3.48 - CLK0 (SRC0) */
+#define DMM32AT_AO_LSB_REG 0x04
+#define DMM32AT_AO_MSB_REG 0x05
+#define DMM32AT_AO_MSB_DACH(x) ((x) << 6)
+#define DMM32AT_FIFO_DEPTH_REG 0x06
+#define DMM32AT_FIFO_CTRL_REG 0x07
+#define DMM32AT_FIFO_CTRL_FIFOEN (1 << 3)
+#define DMM32AT_FIFO_CTRL_SCANEN (1 << 2)
+#define DMM32AT_FIFO_CTRL_FIFORST (1 << 1)
+#define DMM32AT_FIFO_STATUS_REG 0x07
+#define DMM32AT_FIFO_STATUS_EF (1 << 7)
+#define DMM32AT_FIFO_STATUS_HF (1 << 6)
+#define DMM32AT_FIFO_STATUS_FF (1 << 5)
+#define DMM32AT_FIFO_STATUS_OVF (1 << 4)
+#define DMM32AT_FIFO_STATUS_FIFOEN (1 << 3)
+#define DMM32AT_FIFO_STATUS_SCANEN (1 << 2)
+#define DMM32AT_FIFO_STATUS_PAGE_MASK (3 << 0)
+#define DMM32AT_CTRL_REG 0x08
+#define DMM32AT_CTRL_RESETA (1 << 5)
+#define DMM32AT_CTRL_RESETD (1 << 4)
+#define DMM32AT_CTRL_INTRST (1 << 3)
+#define DMM32AT_CTRL_PAGE_8254 (0 << 0)
+#define DMM32AT_CTRL_PAGE_8255 (1 << 0)
+#define DMM32AT_CTRL_PAGE_CALIB (3 << 0)
+#define DMM32AT_AI_STATUS_REG 0x08
+#define DMM32AT_AI_STATUS_STS (1 << 7)
+#define DMM32AT_AI_STATUS_SD1 (1 << 6)
+#define DMM32AT_AI_STATUS_SD0 (1 << 5)
+#define DMM32AT_AI_STATUS_ADCH_MASK (0x1f << 0)
+#define DMM32AT_INTCLK_REG 0x09
+#define DMM32AT_INTCLK_ADINT (1 << 7)
+#define DMM32AT_INTCLK_DINT (1 << 6)
+#define DMM32AT_INTCLK_TINT (1 << 5)
+#define DMM32AT_INTCLK_CLKEN (1 << 1) /* 1=see below 0=software */
+#define DMM32AT_INTCLK_CLKSEL (1 << 0) /* 1=OUT2 0=EXTCLK */
+#define DMM32AT_CTRDIO_CFG_REG 0x0a
+#define DMM32AT_CTRDIO_CFG_FREQ12 (1 << 7) /* CLK12 1=100KHz 0=10MHz */
+#define DMM32AT_CTRDIO_CFG_FREQ0 (1 << 6) /* CLK0 1=10KHz 0=10MHz */
+#define DMM32AT_CTRDIO_CFG_OUT2EN (1 << 5) /* J3.42 1=OUT2 is DOUT2 */
+#define DMM32AT_CTRDIO_CFG_OUT0EN (1 << 4) /* J3,44 1=OUT0 is DOUT0 */
+#define DMM32AT_CTRDIO_CFG_GT0EN (1 << 2) /* J3.47 1=DIN1 is GATE0 */
+#define DMM32AT_CTRDIO_CFG_SRC0 (1 << 1) /* CLK0 is 0=FREQ0 1=J3.48 */
+#define DMM32AT_CTRDIO_CFG_GT12EN (1 << 0) /* J3.46 1=DIN2 is GATE12 */
+#define DMM32AT_AI_CFG_REG 0x0b
+#define DMM32AT_AI_CFG_SCINT_20US (0 << 4)
+#define DMM32AT_AI_CFG_SCINT_15US (1 << 4)
+#define DMM32AT_AI_CFG_SCINT_10US (2 << 4)
+#define DMM32AT_AI_CFG_SCINT_5US (3 << 4)
+#define DMM32AT_AI_CFG_RANGE (1 << 3) /* 0=5V 1=10V */
+#define DMM32AT_AI_CFG_ADBU (1 << 2) /* 0=bipolar 1=unipolar */
+#define DMM32AT_AI_CFG_GAIN(x) ((x) << 0)
+#define DMM32AT_AI_READBACK_REG 0x0b
+#define DMM32AT_AI_READBACK_WAIT (1 << 7) /* DMM32AT_AI_STATUS_STS */
+#define DMM32AT_AI_READBACK_RANGE (1 << 3)
+#define DMM32AT_AI_READBACK_ADBU (1 << 2)
+#define DMM32AT_AI_READBACK_GAIN_MASK (3 << 0)
+
+#define DMM32AT_CLK1 0x0d
+#define DMM32AT_CLK2 0x0e
+#define DMM32AT_CLKCT 0x0f
+
+#define DMM32AT_8255_IOBASE 0x0c /* Page 1 registers */
+
+/* Board register values. */
+
+/* DMM32AT_AI_CFG_REG 0x0b */
+#define DMM32AT_RANGE_U10 0x0c
+#define DMM32AT_RANGE_U5 0x0d
+#define DMM32AT_RANGE_B10 0x08
+#define DMM32AT_RANGE_B5 0x00
+
+/* DMM32AT_CLKCT 0x0f */
+#define DMM32AT_CLKCT1 0x56 /* mode3 counter 1 - write low byte only */
+#define DMM32AT_CLKCT2 0xb6 /* mode3 counter 2 - write high and low byte */
+
+/* board AI ranges in comedi structure */
+static const struct comedi_lrange dmm32at_airanges = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ BIP_RANGE(10),
+ BIP_RANGE(5)
+ }
+};
+
+/* register values for above ranges */
+static const unsigned char dmm32at_rangebits[] = {
+ DMM32AT_RANGE_U10,
+ DMM32AT_RANGE_U5,
+ DMM32AT_RANGE_B10,
+ DMM32AT_RANGE_B5,
+};
+
+/* only one of these ranges is valid, as set by a jumper on the
+ * board. The application should only use the range set by the jumper
+ */
+static const struct comedi_lrange dmm32at_aoranges = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ BIP_RANGE(10),
+ BIP_RANGE(5)
+ }
+};
+
+static void dmm32at_ai_set_chanspec(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec, int nchan)
+{
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int last_chan = (chan + nchan - 1) % s->n_chan;
+
+ outb(DMM32AT_FIFO_CTRL_FIFORST, dev->iobase + DMM32AT_FIFO_CTRL_REG);
+
+ if (nchan > 1)
+ outb(DMM32AT_FIFO_CTRL_SCANEN,
+ dev->iobase + DMM32AT_FIFO_CTRL_REG);
+
+ outb(chan, dev->iobase + DMM32AT_AI_LO_CHAN_REG);
+ outb(last_chan, dev->iobase + DMM32AT_AI_HI_CHAN_REG);
+ outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AI_CFG_REG);
+}
+
+static unsigned int dmm32at_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + DMM32AT_AI_LSB_REG);
+ val |= (inb(dev->iobase + DMM32AT_AI_MSB_REG) << 8);
+
+ /* munge two's complement value to offset binary */
+ return comedi_offset_munge(s, val);
+}
+
+static int dmm32at_ai_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + context);
+ if ((status & DMM32AT_AI_STATUS_STS) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int dmm32at_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ int i;
+
+ dmm32at_ai_set_chanspec(dev, s, insn->chanspec, 1);
+
+ /* wait for circuit to settle */
+ ret = comedi_timeout(dev, s, insn, dmm32at_ai_status,
+ DMM32AT_AI_READBACK_REG);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < insn->n; i++) {
+ outb(0xff, dev->iobase + DMM32AT_AI_START_CONV_REG);
+
+ ret = comedi_timeout(dev, s, insn, dmm32at_ai_status,
+ DMM32AT_AI_STATUS_REG);
+ if (ret)
+ return ret;
+
+ data[i] = dmm32at_ai_get_sample(dev, s);
+ }
+
+ return insn->n;
+}
+
+static int dmm32at_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i) % s->n_chan) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int dmm32at_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 1000000);
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000);
+
+ if (cmd->convert_arg >= 17500)
+ cmd->convert_arg = 20000;
+ else if (cmd->convert_arg >= 12500)
+ cmd->convert_arg = 15000;
+ else if (cmd->convert_arg >= 7500)
+ cmd->convert_arg = 10000;
+ else
+ cmd->convert_arg = 5000;
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg);
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= dmm32at_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec)
+{
+ unsigned char lo1, lo2, hi2;
+ unsigned short both2;
+
+ /* based on 10mhz clock */
+ lo1 = 200;
+ both2 = nansec / 20000;
+ hi2 = (both2 & 0xff00) >> 8;
+ lo2 = both2 & 0x00ff;
+
+ /* set counter clocks to 10MHz, disable all aux dio */
+ outb(0, dev->iobase + DMM32AT_CTRDIO_CFG_REG);
+
+ /* get access to the clock regs */
+ outb(DMM32AT_CTRL_PAGE_8254, dev->iobase + DMM32AT_CTRL_REG);
+
+ /* write the counter 1 control word and low byte to counter */
+ outb(DMM32AT_CLKCT1, dev->iobase + DMM32AT_CLKCT);
+ outb(lo1, dev->iobase + DMM32AT_CLK1);
+
+ /* write the counter 2 control word and low byte then to counter */
+ outb(DMM32AT_CLKCT2, dev->iobase + DMM32AT_CLKCT);
+ outb(lo2, dev->iobase + DMM32AT_CLK2);
+ outb(hi2, dev->iobase + DMM32AT_CLK2);
+
+ /* enable the ai conversion interrupt and the clock to start scans */
+ outb(DMM32AT_INTCLK_ADINT |
+ DMM32AT_INTCLK_CLKEN | DMM32AT_INTCLK_CLKSEL,
+ dev->iobase + DMM32AT_INTCLK_REG);
+}
+
+static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ dmm32at_ai_set_chanspec(dev, s, cmd->chanlist[0], cmd->chanlist_len);
+
+ /* reset the interrupt just in case */
+ outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG);
+
+ /*
+ * wait for circuit to settle
+ * we don't have the 'insn' here but it's not needed
+ */
+ ret = comedi_timeout(dev, s, NULL, dmm32at_ai_status,
+ DMM32AT_AI_READBACK_REG);
+ if (ret)
+ return ret;
+
+ if (cmd->stop_src == TRIG_NONE || cmd->stop_arg > 1) {
+ /* start the clock and enable the interrupts */
+ dmm32at_setaitimer(dev, cmd->scan_begin_arg);
+ } else {
+ /* start the interrupts and initiate a single scan */
+ outb(DMM32AT_INTCLK_ADINT, dev->iobase + DMM32AT_INTCLK_REG);
+ outb(0xff, dev->iobase + DMM32AT_AI_START_CONV_REG);
+ }
+
+ return 0;
+}
+
+static int dmm32at_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ /* disable further interrupts and clocks */
+ outb(0x0, dev->iobase + DMM32AT_INTCLK_REG);
+ return 0;
+}
+
+static irqreturn_t dmm32at_isr(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ unsigned char intstat;
+ unsigned int val;
+ int i;
+
+ if (!dev->attached) {
+ dev_err(dev->class_dev, "spurious interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ intstat = inb(dev->iobase + DMM32AT_INTCLK_REG);
+
+ if (intstat & DMM32AT_INTCLK_ADINT) {
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ val = dmm32at_ai_get_sample(dev, s);
+ comedi_buf_write_samples(s, &val, 1);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+
+ comedi_handle_events(dev, s);
+ }
+
+ /* reset the interrupt */
+ outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG);
+ return IRQ_HANDLED;
+}
+
+static int dmm32at_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + DMM32AT_AUX_DI_REG);
+ if ((status & DMM32AT_AUX_DI_DACBUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int dmm32at_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+ int ret;
+
+ /* write LSB then MSB + chan to load DAC */
+ outb(val & 0xff, dev->iobase + DMM32AT_AO_LSB_REG);
+ outb((val >> 8) | DMM32AT_AO_MSB_DACH(chan),
+ dev->iobase + DMM32AT_AO_MSB_REG);
+
+ /* wait for circuit to settle */
+ ret = comedi_timeout(dev, s, insn, dmm32at_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* dummy read to update DAC */
+ inb(dev->iobase + DMM32AT_AO_MSB_REG);
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int dmm32at_8255_io(struct comedi_device *dev,
+ int dir, int port, int data, unsigned long regbase)
+{
+ /* get access to the DIO regs */
+ outb(DMM32AT_CTRL_PAGE_8255, dev->iobase + DMM32AT_CTRL_REG);
+
+ if (dir) {
+ outb(data, dev->iobase + regbase + port);
+ return 0;
+ }
+ return inb(dev->iobase + regbase + port);
+}
+
+/* Make sure the board is there and put it to a known state */
+static int dmm32at_reset(struct comedi_device *dev)
+{
+ unsigned char aihi, ailo, fifostat, aistat, intstat, airback;
+
+ /* reset the board */
+ outb(DMM32AT_CTRL_RESETA, dev->iobase + DMM32AT_CTRL_REG);
+
+ /* allow a millisecond to reset */
+ udelay(1000);
+
+ /* zero scan and fifo control */
+ outb(0x0, dev->iobase + DMM32AT_FIFO_CTRL_REG);
+
+ /* zero interrupt and clock control */
+ outb(0x0, dev->iobase + DMM32AT_INTCLK_REG);
+
+ /* write a test channel range, the high 3 bits should drop */
+ outb(0x80, dev->iobase + DMM32AT_AI_LO_CHAN_REG);
+ outb(0xff, dev->iobase + DMM32AT_AI_HI_CHAN_REG);
+
+ /* set the range at 10v unipolar */
+ outb(DMM32AT_RANGE_U10, dev->iobase + DMM32AT_AI_CFG_REG);
+
+ /* should take 10 us to settle, here's a hundred */
+ udelay(100);
+
+ /* read back the values */
+ ailo = inb(dev->iobase + DMM32AT_AI_LO_CHAN_REG);
+ aihi = inb(dev->iobase + DMM32AT_AI_HI_CHAN_REG);
+ fifostat = inb(dev->iobase + DMM32AT_FIFO_STATUS_REG);
+ aistat = inb(dev->iobase + DMM32AT_AI_STATUS_REG);
+ intstat = inb(dev->iobase + DMM32AT_INTCLK_REG);
+ airback = inb(dev->iobase + DMM32AT_AI_READBACK_REG);
+
+ /*
+ * NOTE: The (DMM32AT_AI_STATUS_SD1 | DMM32AT_AI_STATUS_SD0)
+ * test makes this driver only work if the board is configured
+ * with all A/D channels set for single-ended operation.
+ */
+ if (ailo != 0x00 || aihi != 0x1f ||
+ fifostat != DMM32AT_FIFO_STATUS_EF ||
+ aistat != (DMM32AT_AI_STATUS_SD1 | DMM32AT_AI_STATUS_SD0) ||
+ intstat != 0x00 || airback != 0x0c)
+ return -EIO;
+
+ return 0;
+}
+
+static int dmm32at_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ ret = dmm32at_reset(dev);
+ if (ret) {
+ dev_err(dev->class_dev, "board detection failed\n");
+ return ret;
+ }
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], dmm32at_isr, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 32;
+ s->maxdata = 0xffff;
+ s->range_table = &dmm32at_airanges;
+ s->insn_read = dmm32at_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmd = dmm32at_ai_cmd;
+ s->do_cmdtest = dmm32at_ai_cmdtest;
+ s->cancel = dmm32at_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->range_table = &dmm32at_aoranges;
+ s->insn_write = dmm32at_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[2];
+ ret = subdev_8255_init(dev, s, dmm32at_8255_io, DMM32AT_8255_IOBASE);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver dmm32at_driver = {
+ .driver_name = "dmm32at",
+ .module = THIS_MODULE,
+ .attach = dmm32at_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dmm32at_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi: Diamond Systems Diamond-MM-32-AT");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt2801.c b/drivers/staging/comedi/drivers/dt2801.c
new file mode 100644
index 000000000..80e38dedd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2801.c
@@ -0,0 +1,639 @@
+/*
+ * comedi/drivers/dt2801.c
+ * Device Driver for DataTranslation DT2801
+ *
+ */
+/*
+Driver: dt2801
+Description: Data Translation DT2801 series and DT01-EZ
+Author: ds
+Status: works
+Devices: [Data Translation] DT2801 (dt2801), DT2801-A, DT2801/5716A,
+ DT2805, DT2805/5716A, DT2808, DT2818, DT2809, DT01-EZ
+
+This driver can autoprobe the type of board.
+
+Configuration options:
+ [0] - I/O port base address
+ [1] - unused
+ [2] - A/D reference 0=differential, 1=single-ended
+ [3] - A/D range
+ 0 = [-10, 10]
+ 1 = [0,10]
+ [4] - D/A 0 range
+ 0 = [-10, 10]
+ 1 = [-5,5]
+ 2 = [-2.5,2.5]
+ 3 = [0,10]
+ 4 = [0,5]
+ [5] - D/A 1 range (same choices)
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+#include <linux/delay.h>
+
+#define DT2801_TIMEOUT 1000
+
+/* Hardware Configuration */
+/* ====================== */
+
+#define DT2801_MAX_DMA_SIZE (64 * 1024)
+
+/* define's */
+/* ====================== */
+
+/* Commands */
+#define DT_C_RESET 0x0
+#define DT_C_CLEAR_ERR 0x1
+#define DT_C_READ_ERRREG 0x2
+#define DT_C_SET_CLOCK 0x3
+
+#define DT_C_TEST 0xb
+#define DT_C_STOP 0xf
+
+#define DT_C_SET_DIGIN 0x4
+#define DT_C_SET_DIGOUT 0x5
+#define DT_C_READ_DIG 0x6
+#define DT_C_WRITE_DIG 0x7
+
+#define DT_C_WRITE_DAIM 0x8
+#define DT_C_SET_DA 0x9
+#define DT_C_WRITE_DA 0xa
+
+#define DT_C_READ_ADIM 0xc
+#define DT_C_SET_AD 0xd
+#define DT_C_READ_AD 0xe
+
+/* Command modifiers (only used with read/write), EXTTRIG can be
+ used with some other commands.
+*/
+#define DT_MOD_DMA (1<<4)
+#define DT_MOD_CONT (1<<5)
+#define DT_MOD_EXTCLK (1<<6)
+#define DT_MOD_EXTTRIG (1<<7)
+
+/* Bits in status register */
+#define DT_S_DATA_OUT_READY (1<<0)
+#define DT_S_DATA_IN_FULL (1<<1)
+#define DT_S_READY (1<<2)
+#define DT_S_COMMAND (1<<3)
+#define DT_S_COMPOSITE_ERROR (1<<7)
+
+/* registers */
+#define DT2801_DATA 0
+#define DT2801_STATUS 1
+#define DT2801_CMD 1
+
+#if 0
+/* ignore 'defined but not used' warning */
+static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+#endif
+static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+#if 0
+/* ignore 'defined but not used' warning */
+static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+#endif
+static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.02)
+ }
+};
+
+struct dt2801_board {
+ const char *name;
+ int boardcode;
+ int ad_diff;
+ int ad_chan;
+ int adbits;
+ int adrangetype;
+ int dabits;
+};
+
+/* Typeid's for the different boards of the DT2801-series
+ (taken from the test-software, that comes with the board)
+ */
+static const struct dt2801_board boardtypes[] = {
+ {
+ .name = "dt2801",
+ .boardcode = 0x09,
+ .ad_diff = 2,
+ .ad_chan = 16,
+ .adbits = 12,
+ .adrangetype = 0,
+ .dabits = 12},
+ {
+ .name = "dt2801-a",
+ .boardcode = 0x52,
+ .ad_diff = 2,
+ .ad_chan = 16,
+ .adbits = 12,
+ .adrangetype = 0,
+ .dabits = 12},
+ {
+ .name = "dt2801/5716a",
+ .boardcode = 0x82,
+ .ad_diff = 1,
+ .ad_chan = 16,
+ .adbits = 16,
+ .adrangetype = 1,
+ .dabits = 12},
+ {
+ .name = "dt2805",
+ .boardcode = 0x12,
+ .ad_diff = 1,
+ .ad_chan = 16,
+ .adbits = 12,
+ .adrangetype = 0,
+ .dabits = 12},
+ {
+ .name = "dt2805/5716a",
+ .boardcode = 0x92,
+ .ad_diff = 1,
+ .ad_chan = 16,
+ .adbits = 16,
+ .adrangetype = 1,
+ .dabits = 12},
+ {
+ .name = "dt2808",
+ .boardcode = 0x20,
+ .ad_diff = 0,
+ .ad_chan = 16,
+ .adbits = 12,
+ .adrangetype = 2,
+ .dabits = 8},
+ {
+ .name = "dt2818",
+ .boardcode = 0xa2,
+ .ad_diff = 0,
+ .ad_chan = 4,
+ .adbits = 12,
+ .adrangetype = 0,
+ .dabits = 12},
+ {
+ .name = "dt2809",
+ .boardcode = 0xb0,
+ .ad_diff = 0,
+ .ad_chan = 8,
+ .adbits = 12,
+ .adrangetype = 1,
+ .dabits = 12},
+};
+
+struct dt2801_private {
+ const struct comedi_lrange *dac_range_types[2];
+};
+
+/* These are the low-level routines:
+ writecommand: write a command to the board
+ writedata: write data byte
+ readdata: read data byte
+ */
+
+/* Only checks DataOutReady-flag, not the Ready-flag as it is done
+ in the examples of the manual. I don't see why this should be
+ necessary. */
+static int dt2801_readdata(struct comedi_device *dev, int *data)
+{
+ int stat = 0;
+ int timeout = DT2801_TIMEOUT;
+
+ do {
+ stat = inb_p(dev->iobase + DT2801_STATUS);
+ if (stat & (DT_S_COMPOSITE_ERROR | DT_S_READY))
+ return stat;
+ if (stat & DT_S_DATA_OUT_READY) {
+ *data = inb_p(dev->iobase + DT2801_DATA);
+ return 0;
+ }
+ } while (--timeout > 0);
+
+ return -ETIME;
+}
+
+static int dt2801_readdata2(struct comedi_device *dev, int *data)
+{
+ int lb = 0;
+ int hb = 0;
+ int ret;
+
+ ret = dt2801_readdata(dev, &lb);
+ if (ret)
+ return ret;
+ ret = dt2801_readdata(dev, &hb);
+ if (ret)
+ return ret;
+
+ *data = (hb << 8) + lb;
+ return 0;
+}
+
+static int dt2801_writedata(struct comedi_device *dev, unsigned int data)
+{
+ int stat = 0;
+ int timeout = DT2801_TIMEOUT;
+
+ do {
+ stat = inb_p(dev->iobase + DT2801_STATUS);
+
+ if (stat & DT_S_COMPOSITE_ERROR)
+ return stat;
+ if (!(stat & DT_S_DATA_IN_FULL)) {
+ outb_p(data & 0xff, dev->iobase + DT2801_DATA);
+ return 0;
+ }
+ } while (--timeout > 0);
+
+ return -ETIME;
+}
+
+static int dt2801_writedata2(struct comedi_device *dev, unsigned int data)
+{
+ int ret;
+
+ ret = dt2801_writedata(dev, data & 0xff);
+ if (ret < 0)
+ return ret;
+ ret = dt2801_writedata(dev, data >> 8);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int dt2801_wait_for_ready(struct comedi_device *dev)
+{
+ int timeout = DT2801_TIMEOUT;
+ int stat;
+
+ stat = inb_p(dev->iobase + DT2801_STATUS);
+ if (stat & DT_S_READY)
+ return 0;
+ do {
+ stat = inb_p(dev->iobase + DT2801_STATUS);
+
+ if (stat & DT_S_COMPOSITE_ERROR)
+ return stat;
+ if (stat & DT_S_READY)
+ return 0;
+ } while (--timeout > 0);
+
+ return -ETIME;
+}
+
+static void dt2801_writecmd(struct comedi_device *dev, int command)
+{
+ int stat;
+
+ dt2801_wait_for_ready(dev);
+
+ stat = inb_p(dev->iobase + DT2801_STATUS);
+ if (stat & DT_S_COMPOSITE_ERROR) {
+ dev_dbg(dev->class_dev,
+ "composite-error in %s, ignoring\n", __func__);
+ }
+ if (!(stat & DT_S_READY))
+ dev_dbg(dev->class_dev, "!ready in %s, ignoring\n", __func__);
+ outb_p(command, dev->iobase + DT2801_CMD);
+}
+
+static int dt2801_reset(struct comedi_device *dev)
+{
+ int board_code = 0;
+ unsigned int stat;
+ int timeout;
+
+ /* pull random data from data port */
+ inb_p(dev->iobase + DT2801_DATA);
+ inb_p(dev->iobase + DT2801_DATA);
+ inb_p(dev->iobase + DT2801_DATA);
+ inb_p(dev->iobase + DT2801_DATA);
+
+ /* dt2801_writecmd(dev,DT_C_STOP); */
+ outb_p(DT_C_STOP, dev->iobase + DT2801_CMD);
+
+ /* dt2801_wait_for_ready(dev); */
+ udelay(100);
+ timeout = 10000;
+ do {
+ stat = inb_p(dev->iobase + DT2801_STATUS);
+ if (stat & DT_S_READY)
+ break;
+ } while (timeout--);
+ if (!timeout)
+ dev_dbg(dev->class_dev, "timeout 1 status=0x%02x\n", stat);
+
+ /* dt2801_readdata(dev,&board_code); */
+
+ outb_p(DT_C_RESET, dev->iobase + DT2801_CMD);
+ /* dt2801_writecmd(dev,DT_C_RESET); */
+
+ udelay(100);
+ timeout = 10000;
+ do {
+ stat = inb_p(dev->iobase + DT2801_STATUS);
+ if (stat & DT_S_READY)
+ break;
+ } while (timeout--);
+ if (!timeout)
+ dev_dbg(dev->class_dev, "timeout 2 status=0x%02x\n", stat);
+
+ dt2801_readdata(dev, &board_code);
+
+ return board_code;
+}
+
+static int probe_number_of_ai_chans(struct comedi_device *dev)
+{
+ int n_chans;
+ int stat;
+ int data;
+
+ for (n_chans = 0; n_chans < 16; n_chans++) {
+ dt2801_writecmd(dev, DT_C_READ_ADIM);
+ dt2801_writedata(dev, 0);
+ dt2801_writedata(dev, n_chans);
+ stat = dt2801_readdata2(dev, &data);
+
+ if (stat)
+ break;
+ }
+
+ dt2801_reset(dev);
+ dt2801_reset(dev);
+
+ return n_chans;
+}
+
+static const struct comedi_lrange *dac_range_table[] = {
+ &range_bipolar10,
+ &range_bipolar5,
+ &range_bipolar2_5,
+ &range_unipolar10,
+ &range_unipolar5
+};
+
+static const struct comedi_lrange *dac_range_lkup(int opt)
+{
+ if (opt < 0 || opt >= 5)
+ return &range_unknown;
+ return dac_range_table[opt];
+}
+
+static const struct comedi_lrange *ai_range_lkup(int type, int opt)
+{
+ switch (type) {
+ case 0:
+ return (opt) ?
+ &range_dt2801_ai_pgl_unipolar :
+ &range_dt2801_ai_pgl_bipolar;
+ case 1:
+ return (opt) ? &range_unipolar10 : &range_bipolar10;
+ case 2:
+ return &range_unipolar5;
+ }
+ return &range_unknown;
+}
+
+static int dt2801_error(struct comedi_device *dev, int stat)
+{
+ if (stat < 0) {
+ if (stat == -ETIME)
+ dev_dbg(dev->class_dev, "timeout\n");
+ else
+ dev_dbg(dev->class_dev, "error %d\n", stat);
+ return stat;
+ }
+ dev_dbg(dev->class_dev, "error status 0x%02x, resetting...\n", stat);
+
+ dt2801_reset(dev);
+ dt2801_reset(dev);
+
+ return -EIO;
+}
+
+static int dt2801_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int d;
+ int stat;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ dt2801_writecmd(dev, DT_C_READ_ADIM);
+ dt2801_writedata(dev, CR_RANGE(insn->chanspec));
+ dt2801_writedata(dev, CR_CHAN(insn->chanspec));
+ stat = dt2801_readdata2(dev, &d);
+
+ if (stat != 0)
+ return dt2801_error(dev, stat);
+
+ data[i] = d;
+ }
+
+ return i;
+}
+
+static int dt2801_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ dt2801_writecmd(dev, DT_C_WRITE_DAIM);
+ dt2801_writedata(dev, chan);
+ dt2801_writedata2(dev, data[0]);
+
+ s->readback[chan] = data[0];
+
+ return 1;
+}
+
+static int dt2801_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int which = (s == &dev->subdevices[3]) ? 1 : 0;
+ unsigned int val = 0;
+
+ if (comedi_dio_update_state(s, data)) {
+ dt2801_writecmd(dev, DT_C_WRITE_DIG);
+ dt2801_writedata(dev, which);
+ dt2801_writedata(dev, s->state);
+ }
+
+ dt2801_writecmd(dev, DT_C_READ_DIG);
+ dt2801_writedata(dev, which);
+ dt2801_readdata(dev, &val);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dt2801_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0xff);
+ if (ret)
+ return ret;
+
+ dt2801_writecmd(dev, s->io_bits ? DT_C_SET_DIGOUT : DT_C_SET_DIGIN);
+ dt2801_writedata(dev, (s == &dev->subdevices[3]) ? 1 : 0);
+
+ return insn->n;
+}
+
+/*
+ options:
+ [0] - i/o base
+ [1] - unused
+ [2] - a/d 0=differential, 1=single-ended
+ [3] - a/d range 0=[-10,10], 1=[0,10]
+ [4] - dac0 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5]
+ [5] - dac1 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5]
+*/
+static int dt2801_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct dt2801_board *board;
+ struct dt2801_private *devpriv;
+ struct comedi_subdevice *s;
+ int board_code, type;
+ int ret = 0;
+ int n_ai_chans;
+
+ ret = comedi_request_region(dev, it->options[0], 0x2);
+ if (ret)
+ return ret;
+
+ /* do some checking */
+
+ board_code = dt2801_reset(dev);
+
+ /* heh. if it didn't work, try it again. */
+ if (!board_code)
+ board_code = dt2801_reset(dev);
+
+ for (type = 0; type < ARRAY_SIZE(boardtypes); type++) {
+ if (boardtypes[type].boardcode == board_code)
+ goto havetype;
+ }
+ dev_dbg(dev->class_dev,
+ "unrecognized board code=0x%02x, contact author\n", board_code);
+ type = 0;
+
+havetype:
+ dev->board_ptr = boardtypes + type;
+ board = dev->board_ptr;
+
+ n_ai_chans = probe_number_of_ai_chans(dev);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ goto out;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->board_name = board->name;
+
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+#if 1
+ s->n_chan = n_ai_chans;
+#else
+ if (it->options[2])
+ s->n_chan = board->ad_chan;
+ else
+ s->n_chan = board->ad_chan / 2;
+#endif
+ s->maxdata = (1 << board->adbits) - 1;
+ s->range_table = ai_range_lkup(board->adrangetype, it->options[3]);
+ s->insn_read = dt2801_ai_insn_read;
+
+ s = &dev->subdevices[1];
+ /* ao subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = (1 << board->dabits) - 1;
+ s->range_table_list = devpriv->dac_range_types;
+ devpriv->dac_range_types[0] = dac_range_lkup(it->options[4]);
+ devpriv->dac_range_types[1] = dac_range_lkup(it->options[5]);
+ s->insn_write = dt2801_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ /* 1st digital subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dt2801_dio_insn_bits;
+ s->insn_config = dt2801_dio_insn_config;
+
+ s = &dev->subdevices[3];
+ /* 2nd digital subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dt2801_dio_insn_bits;
+ s->insn_config = dt2801_dio_insn_config;
+
+ ret = 0;
+out:
+ return ret;
+}
+
+static struct comedi_driver dt2801_driver = {
+ .driver_name = "dt2801",
+ .module = THIS_MODULE,
+ .attach = dt2801_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2801_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt2811.c b/drivers/staging/comedi/drivers/dt2811.c
new file mode 100644
index 000000000..a80773291
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2811.c
@@ -0,0 +1,474 @@
+/*
+ comedi/drivers/dt2811.c
+ Hardware driver for Data Translation DT2811
+
+ COMEDI - Linux Control and Measurement Device Interface
+ History:
+ Base Version - David A. Schleef <ds@schleef.org>
+ December 1998 - Updated to work. David does not have a DT2811
+ board any longer so this was suffering from bitrot.
+ Updated performed by ...
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+/*
+Driver: dt2811
+Description: Data Translation DT2811
+Author: ds
+Devices: [Data Translation] DT2811-PGL (dt2811-pgl), DT2811-PGH (dt2811-pgh)
+Status: works
+
+Configuration options:
+ [0] - I/O port base address
+ [1] - IRQ, although this is currently unused
+ [2] - A/D reference
+ 0 = signle-ended
+ 1 = differential
+ 2 = pseudo-differential (common reference)
+ [3] - A/D range
+ 0 = [-5, 5]
+ 1 = [-2.5, 2.5]
+ 2 = [0, 5]
+ [4] - D/A 0 range (same choices)
+ [4] - D/A 1 range (same choices)
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+static const struct comedi_lrange range_dt2811_pgh_ai_5_unipolar = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ UNI_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = {
+ 4, {
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(0.3125)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgh_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgl_ai_5_unipolar = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.05),
+ UNI_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = {
+ 4, {
+ BIP_RANGE(2.5),
+ BIP_RANGE(0.25),
+ BIP_RANGE(0.025),
+ BIP_RANGE(0.005)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgl_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.01)
+ }
+};
+
+/*
+
+ 0x00 ADCSR R/W A/D Control/Status Register
+ bit 7 - (R) 1 indicates A/D conversion done
+ reading ADDAT clears bit
+ (W) ignored
+ bit 6 - (R) 1 indicates A/D error
+ (W) ignored
+ bit 5 - (R) 1 indicates A/D busy, cleared at end
+ of conversion
+ (W) ignored
+ bit 4 - (R) 0
+ (W)
+ bit 3 - (R) 0
+ bit 2 - (R/W) 1 indicates interrupts enabled
+ bits 1,0 - (R/W) mode bits
+ 00 single conversion on ADGCR load
+ 01 continuous conversion, internal clock,
+ (clock enabled on ADGCR load)
+ 10 continuous conversion, internal clock,
+ external trigger
+ 11 continuous conversion, external clock,
+ external trigger
+
+ 0x01 ADGCR R/W A/D Gain/Channel Register
+ bit 6,7 - (R/W) gain select
+ 00 gain=1, both PGH, PGL models
+ 01 gain=2 PGH, 10 PGL
+ 10 gain=4 PGH, 100 PGL
+ 11 gain=8 PGH, 500 PGL
+ bit 4,5 - reserved
+ bit 3-0 - (R/W) channel select
+ channel number from 0-15
+
+ 0x02,0x03 (R) ADDAT A/D Data Register
+ (W) DADAT0 D/A Data Register 0
+ 0x02 low byte
+ 0x03 high byte
+
+ 0x04,0x05 (W) DADAT0 D/A Data Register 1
+
+ 0x06 (R) DIO0 Digital Input Port 0
+ (W) DIO1 Digital Output Port 1
+
+ 0x07 TMRCTR (R/W) Timer/Counter Register
+ bits 6,7 - reserved
+ bits 5-3 - Timer frequency control (mantissa)
+ 543 divisor freqency (kHz)
+ 000 1 600
+ 001 10 60
+ 010 2 300
+ 011 3 200
+ 100 4 150
+ 101 5 120
+ 110 6 100
+ 111 12 50
+ bits 2-0 - Timer frequency control (exponent)
+ 210 multiply divisor/divide frequency by
+ 000 1
+ 001 10
+ 010 100
+ 011 1000
+ 100 10000
+ 101 100000
+ 110 1000000
+ 111 10000000
+
+ */
+
+#define TIMEOUT 10000
+
+#define DT2811_ADCSR 0
+#define DT2811_ADGCR 1
+#define DT2811_ADDATLO 2
+#define DT2811_ADDATHI 3
+#define DT2811_DADAT0LO 2
+#define DT2811_DADAT0HI 3
+#define DT2811_DADAT1LO 4
+#define DT2811_DADAT1HI 5
+#define DT2811_DIO 6
+#define DT2811_TMRCTR 7
+
+/*
+ * flags
+ */
+
+/* ADCSR */
+
+#define DT2811_ADDONE 0x80
+#define DT2811_ADERROR 0x40
+#define DT2811_ADBUSY 0x20
+#define DT2811_CLRERROR 0x10
+#define DT2811_INTENB 0x04
+#define DT2811_ADMODE 0x03
+
+struct dt2811_board {
+ const char *name;
+ const struct comedi_lrange *bip_5;
+ const struct comedi_lrange *bip_2_5;
+ const struct comedi_lrange *unip_5;
+};
+
+enum { card_2811_pgh, card_2811_pgl };
+
+struct dt2811_private {
+ int ntrig;
+ int curadchan;
+ enum {
+ adc_singleended, adc_diff, adc_pseudo_diff
+ } adc_mux;
+ enum {
+ dac_bipolar_5, dac_bipolar_2_5, dac_unipolar_5
+ } dac_range[2];
+ const struct comedi_lrange *range_type_list[2];
+};
+
+static const struct comedi_lrange *dac_range_types[] = {
+ &range_bipolar5,
+ &range_bipolar2_5,
+ &range_unipolar5
+};
+
+static int dt2811_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DT2811_ADCSR);
+ if ((status & DT2811_ADBUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int dt2811_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ outb(chan, dev->iobase + DT2811_ADGCR);
+
+ ret = comedi_timeout(dev, s, insn, dt2811_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = inb(dev->iobase + DT2811_ADDATLO);
+ data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8;
+ data[i] &= 0xfff;
+ }
+
+ return i;
+}
+
+static int dt2811_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outb(val & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan);
+ outb((val >> 8) & 0xff,
+ dev->iobase + DT2811_DADAT0HI + 2 * chan);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int dt2811_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = inb(dev->iobase + DT2811_DIO);
+
+ return insn->n;
+}
+
+static int dt2811_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DT2811_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+/*
+ options[0] Board base address
+ options[1] IRQ
+ options[2] Input configuration
+ 0 == single-ended
+ 1 == differential
+ 2 == pseudo-differential
+ options[3] Analog input range configuration
+ 0 == bipolar 5 (-5V -- +5V)
+ 1 == bipolar 2.5V (-2.5V -- +2.5V)
+ 2 == unipolar 5V (0V -- +5V)
+ options[4] Analog output 0 range configuration
+ 0 == bipolar 5 (-5V -- +5V)
+ 1 == bipolar 2.5V (-2.5V -- +2.5V)
+ 2 == unipolar 5V (0V -- +5V)
+ options[5] Analog output 1 range configuration
+ 0 == bipolar 5 (-5V -- +5V)
+ 1 == bipolar 2.5V (-2.5V -- +2.5V)
+ 2 == unipolar 5V (0V -- +5V)
+*/
+static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ /* int i; */
+ const struct dt2811_board *board = dev->board_ptr;
+ struct dt2811_private *devpriv;
+ int ret;
+ struct comedi_subdevice *s;
+
+ ret = comedi_request_region(dev, it->options[0], 0x8);
+ if (ret)
+ return ret;
+
+#if 0
+ outb(0, dev->iobase + DT2811_ADCSR);
+ udelay(100);
+ i = inb(dev->iobase + DT2811_ADDATLO);
+ i = inb(dev->iobase + DT2811_ADDATHI);
+#endif
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ switch (it->options[2]) {
+ case 0:
+ devpriv->adc_mux = adc_singleended;
+ break;
+ case 1:
+ devpriv->adc_mux = adc_diff;
+ break;
+ case 2:
+ devpriv->adc_mux = adc_pseudo_diff;
+ break;
+ default:
+ devpriv->adc_mux = adc_singleended;
+ break;
+ }
+ switch (it->options[4]) {
+ case 0:
+ devpriv->dac_range[0] = dac_bipolar_5;
+ break;
+ case 1:
+ devpriv->dac_range[0] = dac_bipolar_2_5;
+ break;
+ case 2:
+ devpriv->dac_range[0] = dac_unipolar_5;
+ break;
+ default:
+ devpriv->dac_range[0] = dac_bipolar_5;
+ break;
+ }
+ switch (it->options[5]) {
+ case 0:
+ devpriv->dac_range[1] = dac_bipolar_5;
+ break;
+ case 1:
+ devpriv->dac_range[1] = dac_bipolar_2_5;
+ break;
+ case 2:
+ devpriv->dac_range[1] = dac_unipolar_5;
+ break;
+ default:
+ devpriv->dac_range[1] = dac_bipolar_5;
+ break;
+ }
+
+ s = &dev->subdevices[0];
+ /* initialize the ADC subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = devpriv->adc_mux == adc_diff ? 8 : 16;
+ s->insn_read = dt2811_ai_insn;
+ s->maxdata = 0xfff;
+ switch (it->options[3]) {
+ case 0:
+ default:
+ s->range_table = board->bip_5;
+ break;
+ case 1:
+ s->range_table = board->bip_2_5;
+ break;
+ case 2:
+ s->range_table = board->unip_5;
+ break;
+ }
+
+ s = &dev->subdevices[1];
+ /* ao subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0xfff;
+ s->range_table_list = devpriv->range_type_list;
+ devpriv->range_type_list[0] = dac_range_types[devpriv->dac_range[0]];
+ devpriv->range_type_list[1] = dac_range_types[devpriv->dac_range[1]];
+ s->insn_write = dt2811_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ /* di subdevice */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->insn_bits = dt2811_di_insn_bits;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+
+ s = &dev->subdevices[3];
+ /* do subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->insn_bits = dt2811_do_insn_bits;
+ s->maxdata = 1;
+ s->state = 0;
+ s->range_table = &range_digital;
+
+ return 0;
+}
+
+static const struct dt2811_board boardtypes[] = {
+ {
+ .name = "dt2811-pgh",
+ .bip_5 = &range_dt2811_pgh_ai_5_bipolar,
+ .bip_2_5 = &range_dt2811_pgh_ai_2_5_bipolar,
+ .unip_5 = &range_dt2811_pgh_ai_5_unipolar,
+ }, {
+ .name = "dt2811-pgl",
+ .bip_5 = &range_dt2811_pgl_ai_5_bipolar,
+ .bip_2_5 = &range_dt2811_pgl_ai_2_5_bipolar,
+ .unip_5 = &range_dt2811_pgl_ai_5_unipolar,
+ },
+};
+
+static struct comedi_driver dt2811_driver = {
+ .driver_name = "dt2811",
+ .module = THIS_MODULE,
+ .attach = dt2811_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct dt2811_board),
+};
+module_comedi_driver(dt2811_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt2814.c b/drivers/staging/comedi/drivers/dt2814.c
new file mode 100644
index 000000000..66705f9a0
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2814.c
@@ -0,0 +1,297 @@
+/*
+ comedi/drivers/dt2814.c
+ Hardware driver for Data Translation DT2814
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: dt2814
+Description: Data Translation DT2814
+Author: ds
+Status: complete
+Devices: [Data Translation] DT2814 (dt2814)
+
+Configuration options:
+ [0] - I/O port base address
+ [1] - IRQ
+
+This card has 16 analog inputs multiplexed onto a 12 bit ADC. There
+is a minimally useful onboard clock. The base frequency for the
+clock is selected by jumpers, and the clock divider can be selected
+via programmed I/O. Unfortunately, the clock divider can only be
+a power of 10, from 1 to 10^7, of which only 3 or 4 are useful. In
+addition, the clock does not seem to be very accurate.
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+#define DT2814_CSR 0
+#define DT2814_DATA 1
+
+/*
+ * flags
+ */
+
+#define DT2814_FINISH 0x80
+#define DT2814_ERR 0x40
+#define DT2814_BUSY 0x20
+#define DT2814_ENB 0x10
+#define DT2814_CHANMASK 0x0f
+
+struct dt2814_private {
+ int ntrig;
+ int curadchan;
+};
+
+#define DT2814_TIMEOUT 10
+#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */
+
+static int dt2814_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DT2814_CSR);
+ if (status & DT2814_FINISH)
+ return 0;
+ return -EBUSY;
+}
+
+static int dt2814_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int n, hi, lo;
+ int chan;
+ int ret;
+
+ for (n = 0; n < insn->n; n++) {
+ chan = CR_CHAN(insn->chanspec);
+
+ outb(chan, dev->iobase + DT2814_CSR);
+
+ ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ hi = inb(dev->iobase + DT2814_DATA);
+ lo = inb(dev->iobase + DT2814_DATA);
+
+ data[n] = (hi << 4) | (lo >> 4);
+ }
+
+ return n;
+}
+
+static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags)
+{
+ int i;
+ unsigned int f;
+
+ /* XXX ignores flags */
+
+ f = 10000; /* ns */
+ for (i = 0; i < 8; i++) {
+ if ((2 * (*ns)) < (f * 11))
+ break;
+ f *= 10;
+ }
+
+ *ns = f;
+
+ return i;
+}
+
+static int dt2814_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000);
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ DT2814_MAX_SPEED);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->scan_begin_arg;
+ dt2814_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dt2814_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int chan;
+ int trigvar;
+
+ trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
+
+ chan = CR_CHAN(cmd->chanlist[0]);
+
+ devpriv->ntrig = cmd->stop_arg;
+ outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR);
+
+ return 0;
+}
+
+static irqreturn_t dt2814_interrupt(int irq, void *d)
+{
+ int lo, hi;
+ struct comedi_device *dev = d;
+ struct dt2814_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int data;
+
+ if (!dev->attached) {
+ dev_err(dev->class_dev, "spurious interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ hi = inb(dev->iobase + DT2814_DATA);
+ lo = inb(dev->iobase + DT2814_DATA);
+
+ data = (hi << 4) | (lo >> 4);
+
+ if (!(--devpriv->ntrig)) {
+ int i;
+
+ outb(0, dev->iobase + DT2814_CSR);
+ /* note: turning off timed mode triggers another
+ sample. */
+
+ for (i = 0; i < DT2814_TIMEOUT; i++) {
+ if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH)
+ break;
+ }
+ inb(dev->iobase + DT2814_DATA);
+ inb(dev->iobase + DT2814_DATA);
+
+ s->async->events |= COMEDI_CB_EOA;
+ }
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct dt2814_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ ret = comedi_request_region(dev, it->options[0], 0x2);
+ if (ret)
+ return ret;
+
+ outb(0, dev->iobase + DT2814_CSR);
+ udelay(100);
+ if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) {
+ dev_err(dev->class_dev, "reset error (fatal)\n");
+ return -EIO;
+ }
+ i = inb(dev->iobase + DT2814_DATA);
+ i = inb(dev->iobase + DT2814_DATA);
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], dt2814_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 16; /* XXX */
+ s->insn_read = dt2814_ai_insn_read;
+ s->maxdata = 0xfff;
+ s->range_table = &range_unknown; /* XXX */
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->do_cmd = dt2814_ai_cmd;
+ s->do_cmdtest = dt2814_ai_cmdtest;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver dt2814_driver = {
+ .driver_name = "dt2814",
+ .module = THIS_MODULE,
+ .attach = dt2814_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2814_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt2815.c b/drivers/staging/comedi/drivers/dt2815.c
new file mode 100644
index 000000000..fb08569c1
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2815.c
@@ -0,0 +1,223 @@
+/*
+ comedi/drivers/dt2815.c
+ Hardware driver for Data Translation DT2815
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+/*
+Driver: dt2815
+Description: Data Translation DT2815
+Author: ds
+Status: mostly complete, untested
+Devices: [Data Translation] DT2815 (dt2815)
+
+I'm not sure anyone has ever tested this board. If you have information
+contrary, please update.
+
+Configuration options:
+ [0] - I/O port base base address
+ [1] - IRQ (unused)
+ [2] - Voltage unipolar/bipolar configuration
+ 0 == unipolar 5V (0V -- +5V)
+ 1 == bipolar 5V (-5V -- +5V)
+ [3] - Current offset configuration
+ 0 == disabled (0mA -- +32mAV)
+ 1 == enabled (+4mA -- +20mAV)
+ [4] - Firmware program configuration
+ 0 == program 1 (see manual table 5-4)
+ 1 == program 2 (see manual table 5-4)
+ 2 == program 3 (see manual table 5-4)
+ 3 == program 4 (see manual table 5-4)
+ [5] - Analog output 0 range configuration
+ 0 == voltage
+ 1 == current
+ [6] - Analog output 1 range configuration (same options)
+ [7] - Analog output 2 range configuration (same options)
+ [8] - Analog output 3 range configuration (same options)
+ [9] - Analog output 4 range configuration (same options)
+ [10] - Analog output 5 range configuration (same options)
+ [11] - Analog output 6 range configuration (same options)
+ [12] - Analog output 7 range configuration (same options)
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+#define DT2815_DATA 0
+#define DT2815_STATUS 1
+
+struct dt2815_private {
+ const struct comedi_lrange *range_type_list[8];
+ unsigned int ao_readback[8];
+};
+
+static int dt2815_ao_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DT2815_STATUS);
+ if (status == context)
+ return 0;
+ return -EBUSY;
+}
+
+static int dt2815_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dt2815_private *devpriv = dev->private;
+ int i;
+ int chan = CR_CHAN(insn->chanspec);
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return i;
+}
+
+static int dt2815_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dt2815_private *devpriv = dev->private;
+ int i;
+ int chan = CR_CHAN(insn->chanspec);
+ unsigned int lo, hi;
+ int ret;
+
+ for (i = 0; i < insn->n; i++) {
+ lo = ((data[i] & 0x0f) << 4) | (chan << 1) | 0x01;
+ hi = (data[i] & 0xff0) >> 4;
+
+ ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x00);
+ if (ret)
+ return ret;
+
+ outb(lo, dev->iobase + DT2815_DATA);
+
+ ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x10);
+ if (ret)
+ return ret;
+
+ devpriv->ao_readback[chan] = data[i];
+ }
+ return i;
+}
+
+/*
+ options[0] Board base address
+ options[1] IRQ (not applicable)
+ options[2] Voltage unipolar/bipolar configuration
+ 0 == unipolar 5V (0V -- +5V)
+ 1 == bipolar 5V (-5V -- +5V)
+ options[3] Current offset configuration
+ 0 == disabled (0mA -- +32mAV)
+ 1 == enabled (+4mA -- +20mAV)
+ options[4] Firmware program configuration
+ 0 == program 1 (see manual table 5-4)
+ 1 == program 2 (see manual table 5-4)
+ 2 == program 3 (see manual table 5-4)
+ 3 == program 4 (see manual table 5-4)
+ options[5] Analog output 0 range configuration
+ 0 == voltage
+ 1 == current
+ options[6] Analog output 1 range configuration
+ ...
+ options[12] Analog output 7 range configuration
+ 0 == voltage
+ 1 == current
+ */
+
+static int dt2815_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct dt2815_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ const struct comedi_lrange *current_range_type, *voltage_range_type;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x2);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ s = &dev->subdevices[0];
+ /* ao subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->maxdata = 0xfff;
+ s->n_chan = 8;
+ s->insn_write = dt2815_ao_insn;
+ s->insn_read = dt2815_ao_insn_read;
+ s->range_table_list = devpriv->range_type_list;
+
+ current_range_type = (it->options[3])
+ ? &range_4_20mA : &range_0_32mA;
+ voltage_range_type = (it->options[2])
+ ? &range_bipolar5 : &range_unipolar5;
+ for (i = 0; i < 8; i++) {
+ devpriv->range_type_list[i] = (it->options[5 + i])
+ ? current_range_type : voltage_range_type;
+ }
+
+ /* Init the 2815 */
+ outb(0x00, dev->iobase + DT2815_STATUS);
+ for (i = 0; i < 100; i++) {
+ /* This is incredibly slow (approx 20 ms) */
+ unsigned int status;
+
+ udelay(1000);
+ status = inb(dev->iobase + DT2815_STATUS);
+ if (status == 4) {
+ unsigned int program;
+
+ program = (it->options[4] & 0x3) << 3 | 0x7;
+ outb(program, dev->iobase + DT2815_DATA);
+ dev_dbg(dev->class_dev, "program: 0x%x (@t=%d)\n",
+ program, i);
+ break;
+ } else if (status != 0x00) {
+ dev_dbg(dev->class_dev,
+ "unexpected status 0x%x (@t=%d)\n",
+ status, i);
+ if (status & 0x60)
+ outb(0x00, dev->iobase + DT2815_STATUS);
+ }
+ }
+
+ return 0;
+}
+
+static struct comedi_driver dt2815_driver = {
+ .driver_name = "dt2815",
+ .module = THIS_MODULE,
+ .attach = dt2815_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2815_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt2817.c b/drivers/staging/comedi/drivers/dt2817.c
new file mode 100644
index 000000000..5131deebf
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2817.c
@@ -0,0 +1,149 @@
+/*
+ comedi/drivers/dt2817.c
+ Hardware driver for Data Translation DT2817
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: dt2817
+Description: Data Translation DT2817
+Author: ds
+Status: complete
+Devices: [Data Translation] DT2817 (dt2817)
+
+A very simple digital I/O card. Four banks of 8 lines, each bank
+is configurable for input or output. One wonders why it takes a
+50 page manual to describe this thing.
+
+The driver (which, btw, is much less than 50 pages) has 1 subdevice
+with 32 channels, configurable in groups of 8.
+
+Configuration options:
+ [0] - I/O port base base address
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#define DT2817_CR 0
+#define DT2817_DATA 1
+
+static int dt2817_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int oe = 0;
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ if (s->io_bits & 0x000000ff)
+ oe |= 0x1;
+ if (s->io_bits & 0x0000ff00)
+ oe |= 0x2;
+ if (s->io_bits & 0x00ff0000)
+ oe |= 0x4;
+ if (s->io_bits & 0xff000000)
+ oe |= 0x8;
+
+ outb(oe, dev->iobase + DT2817_CR);
+
+ return insn->n;
+}
+
+static int dt2817_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long iobase = dev->iobase + DT2817_DATA;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x000000ff)
+ outb(s->state & 0xff, iobase + 0);
+ if (mask & 0x0000ff00)
+ outb((s->state >> 8) & 0xff, iobase + 1);
+ if (mask & 0x00ff0000)
+ outb((s->state >> 16) & 0xff, iobase + 2);
+ if (mask & 0xff000000)
+ outb((s->state >> 24) & 0xff, iobase + 3);
+ }
+
+ val = inb(iobase + 0);
+ val |= (inb(iobase + 1) << 8);
+ val |= (inb(iobase + 2) << 16);
+ val |= (inb(iobase + 3) << 24);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dt2817_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ int ret;
+ struct comedi_subdevice *s;
+
+ ret = comedi_request_region(dev, it->options[0], 0x5);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+
+ s->n_chan = 32;
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->range_table = &range_digital;
+ s->maxdata = 1;
+ s->insn_bits = dt2817_dio_insn_bits;
+ s->insn_config = dt2817_dio_insn_config;
+
+ s->state = 0;
+ outb(0, dev->iobase + DT2817_CR);
+
+ return 0;
+}
+
+static struct comedi_driver dt2817_driver = {
+ .driver_name = "dt2817",
+ .module = THIS_MODULE,
+ .attach = dt2817_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2817_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt282x.c b/drivers/staging/comedi/drivers/dt282x.c
new file mode 100644
index 000000000..5a536a000
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt282x.c
@@ -0,0 +1,1211 @@
+/*
+ * dt282x.c
+ * Comedi driver for Data Translation DT2821 series
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: dt282x
+ * Description: Data Translation DT2821 series (including DT-EZ)
+ * Author: ds
+ * Devices: [Data Translation] DT2821 (dt2821), DT2821-F-16SE (dt2821-f),
+ * DT2821-F-8DI (dt2821-f), DT2821-G-16SE (dt2821-g),
+ * DT2821-G-8DI (dt2821-g), DT2823 (dt2823), DT2824-PGH (dt2824-pgh),
+ * DT2824-PGL (dt2824-pgl), DT2825 (dt2825), DT2827 (dt2827),
+ * DT2828 (dt2828), DT2928 (dt2829), DT21-EZ (dt21-ez), DT23-EZ (dt23-ez),
+ * DT24-EZ (dt24-ez), DT24-EZ-PGL (dt24-ez-pgl)
+ * Status: complete
+ * Updated: Wed, 22 Aug 2001 17:11:34 -0700
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional, required for async command support)
+ * [2] - DMA 1 (optional, required for async command support)
+ * [3] - DMA 2 (optional, required for async command support)
+ * [4] - AI jumpered for 0=single ended, 1=differential
+ * [5] - AI jumpered for 0=straight binary, 1=2's complement
+ * [6] - AO 0 data format (deprecated, see below)
+ * [7] - AO 1 data format (deprecated, see below)
+ * [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5]
+ * [9] - AO channel 0 range (deprecated, see below)
+ * [10]- AO channel 1 range (deprecated, see below)
+ *
+ * Notes:
+ * - AO commands might be broken.
+ * - If you try to run a command on both the AI and AO subdevices
+ * simultaneously, bad things will happen. The driver needs to
+ * be fixed to check for this situation and return an error.
+ * - AO range is not programmable. The AO subdevice has a range_table
+ * containing all the possible analog output ranges. Use the range
+ * that matches your board configuration to convert between data
+ * values and physical units. The format of the data written to the
+ * board is handled automatically based on the unipolar/bipolar
+ * range that is selected.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+
+/*
+ * Register map
+ */
+#define DT2821_ADCSR_REG 0x00
+#define DT2821_ADCSR_ADERR (1 << 15)
+#define DT2821_ADCSR_ADCLK (1 << 9)
+#define DT2821_ADCSR_MUXBUSY (1 << 8)
+#define DT2821_ADCSR_ADDONE (1 << 7)
+#define DT2821_ADCSR_IADDONE (1 << 6)
+#define DT2821_ADCSR_GS(x) (((x) & 0x3) << 4)
+#define DT2821_ADCSR_CHAN(x) (((x) & 0xf) << 0)
+#define DT2821_CHANCSR_REG 0x02
+#define DT2821_CHANCSR_LLE (1 << 15)
+#define DT2821_CHANCSR_PRESLA(x) (((x) & 0xf) >> 8)
+#define DT2821_CHANCSR_NUMB(x) ((((x) - 1) & 0xf) << 0)
+#define DT2821_ADDAT_REG 0x04
+#define DT2821_DACSR_REG 0x06
+#define DT2821_DACSR_DAERR (1 << 15)
+#define DT2821_DACSR_YSEL(x) ((x) << 9)
+#define DT2821_DACSR_SSEL (1 << 8)
+#define DT2821_DACSR_DACRDY (1 << 7)
+#define DT2821_DACSR_IDARDY (1 << 6)
+#define DT2821_DACSR_DACLK (1 << 5)
+#define DT2821_DACSR_HBOE (1 << 1)
+#define DT2821_DACSR_LBOE (1 << 0)
+#define DT2821_DADAT_REG 0x08
+#define DT2821_DIODAT_REG 0x0a
+#define DT2821_SUPCSR_REG 0x0c
+#define DT2821_SUPCSR_DMAD (1 << 15)
+#define DT2821_SUPCSR_ERRINTEN (1 << 14)
+#define DT2821_SUPCSR_CLRDMADNE (1 << 13)
+#define DT2821_SUPCSR_DDMA (1 << 12)
+#define DT2821_SUPCSR_DS_PIO (0 << 10)
+#define DT2821_SUPCSR_DS_AD_CLK (1 << 10)
+#define DT2821_SUPCSR_DS_DA_CLK (2 << 10)
+#define DT2821_SUPCSR_DS_AD_TRIG (3 << 10)
+#define DT2821_SUPCSR_BUFFB (1 << 9)
+#define DT2821_SUPCSR_SCDN (1 << 8)
+#define DT2821_SUPCSR_DACON (1 << 7)
+#define DT2821_SUPCSR_ADCINIT (1 << 6)
+#define DT2821_SUPCSR_DACINIT (1 << 5)
+#define DT2821_SUPCSR_PRLD (1 << 4)
+#define DT2821_SUPCSR_STRIG (1 << 3)
+#define DT2821_SUPCSR_XTRIG (1 << 2)
+#define DT2821_SUPCSR_XCLK (1 << 1)
+#define DT2821_SUPCSR_BDINIT (1 << 0)
+#define DT2821_TMRCTR_REG 0x0e
+
+static const struct comedi_lrange range_dt282x_ai_lo_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_lo_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_5_unipolar = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ UNI_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_hi_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_hi_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.02)
+ }
+};
+
+/*
+ * The Analog Output range is set per-channel using jumpers on the board.
+ * All of these ranges may not be available on some DT2821 series boards.
+ * The default jumper setting has both channels set for +/-10V output.
+ */
+static const struct comedi_lrange dt282x_ao_range = {
+ 5, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ }
+};
+
+struct dt282x_board {
+ const char *name;
+ unsigned int ai_maxdata;
+ int adchan_se;
+ int adchan_di;
+ int ai_speed;
+ int ispgl;
+ int dachan;
+ unsigned int ao_maxdata;
+};
+
+static const struct dt282x_board boardtypes[] = {
+ {
+ .name = "dt2821",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 20000,
+ .dachan = 2,
+ .ao_maxdata = 0x0fff,
+ }, {
+ .name = "dt2821-f",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 6500,
+ .dachan = 2,
+ .ao_maxdata = 0x0fff,
+ }, {
+ .name = "dt2821-g",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 4000,
+ .dachan = 2,
+ .ao_maxdata = 0x0fff,
+ }, {
+ .name = "dt2823",
+ .ai_maxdata = 0xffff,
+ .adchan_di = 4,
+ .ai_speed = 10000,
+ .dachan = 2,
+ .ao_maxdata = 0xffff,
+ }, {
+ .name = "dt2824-pgh",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 20000,
+ }, {
+ .name = "dt2824-pgl",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 20000,
+ .ispgl = 1,
+ }, {
+ .name = "dt2825",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 20000,
+ .ispgl = 1,
+ .dachan = 2,
+ .ao_maxdata = 0x0fff,
+ }, {
+ .name = "dt2827",
+ .ai_maxdata = 0xffff,
+ .adchan_di = 4,
+ .ai_speed = 10000,
+ .dachan = 2,
+ .ao_maxdata = 0x0fff,
+ }, {
+ .name = "dt2828",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 4,
+ .ai_speed = 10000,
+ .dachan = 2,
+ .ao_maxdata = 0x0fff,
+ }, {
+ .name = "dt2829",
+ .ai_maxdata = 0xffff,
+ .adchan_se = 8,
+ .ai_speed = 33250,
+ .dachan = 2,
+ .ao_maxdata = 0xffff,
+ }, {
+ .name = "dt21-ez",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 10000,
+ .dachan = 2,
+ .ao_maxdata = 0x0fff,
+ }, {
+ .name = "dt23-ez",
+ .ai_maxdata = 0xffff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 10000,
+ }, {
+ .name = "dt24-ez",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 10000,
+ }, {
+ .name = "dt24-ez-pgl",
+ .ai_maxdata = 0x0fff,
+ .adchan_se = 16,
+ .adchan_di = 8,
+ .ai_speed = 10000,
+ .ispgl = 1,
+ },
+};
+
+struct dt282x_private {
+ struct comedi_isadma *dma;
+ unsigned int ad_2scomp:1;
+ unsigned int divisor;
+ int dacsr; /* software copies of registers */
+ int adcsr;
+ int supcsr;
+ int ntrig;
+ int nread;
+ int dma_dir;
+};
+
+static int dt282x_prep_ai_dma(struct comedi_device *dev, int dma_index, int n)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma_index];
+
+ if (!devpriv->ntrig)
+ return 0;
+
+ if (n == 0)
+ n = desc->maxsize;
+ if (n > devpriv->ntrig * 2)
+ n = devpriv->ntrig * 2;
+ devpriv->ntrig -= n / 2;
+
+ desc->size = n;
+ comedi_isadma_set_mode(desc, devpriv->dma_dir);
+
+ comedi_isadma_program(desc);
+
+ return n;
+}
+
+static int dt282x_prep_ao_dma(struct comedi_device *dev, int dma_index, int n)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma_index];
+
+ desc->size = n;
+ comedi_isadma_set_mode(desc, devpriv->dma_dir);
+
+ comedi_isadma_program(desc);
+
+ return n;
+}
+
+static void dt282x_disable_dma(struct comedi_device *dev)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ desc = &dma->desc[i];
+ comedi_isadma_disable(desc->chan);
+ }
+}
+
+static unsigned int dt282x_ns_to_timer(unsigned int *ns, unsigned int flags)
+{
+ unsigned int prescale, base, divider;
+
+ for (prescale = 0; prescale < 16; prescale++) {
+ if (prescale == 1)
+ continue;
+ base = 250 * (1 << prescale);
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ divider = (*ns + base / 2) / base;
+ break;
+ case CMDF_ROUND_DOWN:
+ divider = (*ns) / base;
+ break;
+ case CMDF_ROUND_UP:
+ divider = (*ns + base - 1) / base;
+ break;
+ }
+ if (divider < 256) {
+ *ns = divider * base;
+ return (prescale << 8) | (255 - divider);
+ }
+ }
+ base = 250 * (1 << 15);
+ divider = 255;
+ *ns = divider * base;
+ return (15 << 8) | (255 - divider);
+}
+
+static void dt282x_munge(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *buf,
+ unsigned int nbytes)
+{
+ struct dt282x_private *devpriv = dev->private;
+ unsigned int val;
+ int i;
+
+ if (nbytes % 2)
+ dev_err(dev->class_dev,
+ "bug! odd number of bytes from dma xfer\n");
+
+ for (i = 0; i < nbytes / 2; i++) {
+ val = buf[i];
+ val &= s->maxdata;
+ if (devpriv->ad_2scomp)
+ val = comedi_offset_munge(s, val);
+
+ buf[i] = val;
+ }
+}
+
+static unsigned int dt282x_ao_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ int cur_dma)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[cur_dma];
+ unsigned int nsamples = comedi_bytes_to_samples(s, desc->maxsize);
+ unsigned int nbytes;
+
+ nbytes = comedi_buf_read_samples(s, desc->virt_addr, nsamples);
+ if (nbytes)
+ dt282x_prep_ao_dma(dev, cur_dma, nbytes);
+ else
+ dev_err(dev->class_dev, "AO underrun\n");
+
+ return nbytes;
+}
+
+static void dt282x_ao_dma_interrupt(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+
+ outw(devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE,
+ dev->iobase + DT2821_SUPCSR_REG);
+
+ comedi_isadma_disable(desc->chan);
+
+ if (!dt282x_ao_setup_dma(dev, s, dma->cur_dma))
+ s->async->events |= COMEDI_CB_OVERFLOW;
+
+ dma->cur_dma = 1 - dma->cur_dma;
+}
+
+static void dt282x_ai_dma_interrupt(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned int nsamples = comedi_bytes_to_samples(s, desc->size);
+ int ret;
+
+ outw(devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE,
+ dev->iobase + DT2821_SUPCSR_REG);
+
+ comedi_isadma_disable(desc->chan);
+
+ dt282x_munge(dev, s, desc->virt_addr, desc->size);
+ ret = comedi_buf_write_samples(s, desc->virt_addr, nsamples);
+ if (ret != desc->size)
+ return;
+
+ devpriv->nread -= nsamples;
+ if (devpriv->nread < 0) {
+ dev_info(dev->class_dev, "nread off by one\n");
+ devpriv->nread = 0;
+ }
+ if (!devpriv->nread) {
+ s->async->events |= COMEDI_CB_EOA;
+ return;
+ }
+#if 0
+ /* clear the dual dma flag, making this the last dma segment */
+ /* XXX probably wrong */
+ if (!devpriv->ntrig) {
+ devpriv->supcsr &= ~DT2821_SUPCSR_DDMA;
+ outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG);
+ }
+#endif
+ /* restart the channel */
+ dt282x_prep_ai_dma(dev, dma->cur_dma, 0);
+
+ dma->cur_dma = 1 - dma->cur_dma;
+}
+
+static irqreturn_t dt282x_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_subdevice *s_ao = dev->write_subdev;
+ unsigned int supcsr, adcsr, dacsr;
+ int handled = 0;
+
+ if (!dev->attached) {
+ dev_err(dev->class_dev, "spurious interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ adcsr = inw(dev->iobase + DT2821_ADCSR_REG);
+ dacsr = inw(dev->iobase + DT2821_DACSR_REG);
+ supcsr = inw(dev->iobase + DT2821_SUPCSR_REG);
+ if (supcsr & DT2821_SUPCSR_DMAD) {
+ if (devpriv->dma_dir == COMEDI_ISADMA_READ)
+ dt282x_ai_dma_interrupt(dev, s);
+ else
+ dt282x_ao_dma_interrupt(dev, s_ao);
+ handled = 1;
+ }
+ if (adcsr & DT2821_ADCSR_ADERR) {
+ if (devpriv->nread != 0) {
+ dev_err(dev->class_dev, "A/D error\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ }
+ handled = 1;
+ }
+ if (dacsr & DT2821_DACSR_DAERR) {
+ dev_err(dev->class_dev, "D/A error\n");
+ s_ao->async->events |= COMEDI_CB_ERROR;
+ handled = 1;
+ }
+#if 0
+ if (adcsr & DT2821_ADCSR_ADDONE) {
+ unsigned short data;
+
+ data = inw(dev->iobase + DT2821_ADDAT_REG);
+ data &= s->maxdata;
+ if (devpriv->ad_2scomp)
+ data = comedi_offset_munge(s, data);
+
+ comedi_buf_write_samples(s, &data, 1);
+
+ devpriv->nread--;
+ if (!devpriv->nread) {
+ s->async->events |= COMEDI_CB_EOA;
+ } else {
+ if (supcsr & DT2821_SUPCSR_SCDN)
+ outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
+ dev->iobase + DT2821_SUPCSR_REG);
+ }
+ handled = 1;
+ }
+#endif
+ comedi_handle_events(dev, s);
+ comedi_handle_events(dev, s_ao);
+
+ return IRQ_RETVAL(handled);
+}
+
+static void dt282x_load_changain(struct comedi_device *dev, int n,
+ unsigned int *chanlist)
+{
+ struct dt282x_private *devpriv = dev->private;
+ int i;
+
+ outw(DT2821_CHANCSR_LLE | DT2821_CHANCSR_NUMB(n),
+ dev->iobase + DT2821_CHANCSR_REG);
+ for (i = 0; i < n; i++) {
+ unsigned int chan = CR_CHAN(chanlist[i]);
+ unsigned int range = CR_RANGE(chanlist[i]);
+
+ outw(devpriv->adcsr |
+ DT2821_ADCSR_GS(range) |
+ DT2821_ADCSR_CHAN(chan),
+ dev->iobase + DT2821_ADCSR_REG);
+ }
+ outw(DT2821_CHANCSR_NUMB(n), dev->iobase + DT2821_CHANCSR_REG);
+}
+
+static int dt282x_ai_timeout(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + DT2821_ADCSR_REG);
+ switch (context) {
+ case DT2821_ADCSR_MUXBUSY:
+ if ((status & DT2821_ADCSR_MUXBUSY) == 0)
+ return 0;
+ break;
+ case DT2821_ADCSR_ADDONE:
+ if (status & DT2821_ADCSR_ADDONE)
+ return 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return -EBUSY;
+}
+
+/*
+ * Performs a single A/D conversion.
+ * - Put channel/gain into channel-gain list
+ * - preload multiplexer
+ * - trigger conversion and wait for it to finish
+ */
+static int dt282x_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dt282x_private *devpriv = dev->private;
+ unsigned int val;
+ int ret;
+ int i;
+
+ /* XXX should we really be enabling the ad clock here? */
+ devpriv->adcsr = DT2821_ADCSR_ADCLK;
+ outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG);
+
+ dt282x_load_changain(dev, 1, &insn->chanspec);
+
+ outw(devpriv->supcsr | DT2821_SUPCSR_PRLD,
+ dev->iobase + DT2821_SUPCSR_REG);
+ ret = comedi_timeout(dev, s, insn,
+ dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < insn->n; i++) {
+ outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
+ dev->iobase + DT2821_SUPCSR_REG);
+
+ ret = comedi_timeout(dev, s, insn,
+ dt282x_ai_timeout, DT2821_ADCSR_ADDONE);
+ if (ret)
+ return ret;
+
+ val = inw(dev->iobase + DT2821_ADDAT_REG);
+ val &= s->maxdata;
+ if (devpriv->ad_2scomp)
+ val = comedi_offset_munge(s, val);
+
+ data[i] = val;
+ }
+
+ return i;
+}
+
+static int dt282x_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct dt282x_board *board = dev->board_ptr;
+ struct dt282x_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 4000);
+
+#define SLOWEST_TIMER (250*(1<<15)*255)
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER);
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, board->ai_speed);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_EXT | TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->convert_arg;
+ devpriv->divisor = dt282x_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ dt282x_disable_dma(dev);
+
+ outw(devpriv->divisor, dev->iobase + DT2821_TMRCTR_REG);
+
+ devpriv->supcsr = DT2821_SUPCSR_ERRINTEN;
+ if (cmd->scan_begin_src == TRIG_FOLLOW)
+ devpriv->supcsr = DT2821_SUPCSR_DS_AD_CLK;
+ else
+ devpriv->supcsr = DT2821_SUPCSR_DS_AD_TRIG;
+ outw(devpriv->supcsr |
+ DT2821_SUPCSR_CLRDMADNE |
+ DT2821_SUPCSR_BUFFB |
+ DT2821_SUPCSR_ADCINIT,
+ dev->iobase + DT2821_SUPCSR_REG);
+
+ devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg;
+ devpriv->nread = devpriv->ntrig;
+
+ devpriv->dma_dir = COMEDI_ISADMA_READ;
+ dma->cur_dma = 0;
+ dt282x_prep_ai_dma(dev, 0, 0);
+ if (devpriv->ntrig) {
+ dt282x_prep_ai_dma(dev, 1, 0);
+ devpriv->supcsr |= DT2821_SUPCSR_DDMA;
+ outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG);
+ }
+
+ devpriv->adcsr = 0;
+
+ dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist);
+
+ devpriv->adcsr = DT2821_ADCSR_ADCLK | DT2821_ADCSR_IADDONE;
+ outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG);
+
+ outw(devpriv->supcsr | DT2821_SUPCSR_PRLD,
+ dev->iobase + DT2821_SUPCSR_REG);
+ ret = comedi_timeout(dev, s, NULL,
+ dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY);
+ if (ret)
+ return ret;
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) {
+ outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
+ dev->iobase + DT2821_SUPCSR_REG);
+ } else {
+ devpriv->supcsr |= DT2821_SUPCSR_XTRIG;
+ outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG);
+ }
+
+ return 0;
+}
+
+static int dt282x_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ dt282x_disable_dma(dev);
+
+ devpriv->adcsr = 0;
+ outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG);
+
+ devpriv->supcsr = 0;
+ outw(devpriv->supcsr | DT2821_SUPCSR_ADCINIT,
+ dev->iobase + DT2821_SUPCSR_REG);
+
+ return 0;
+}
+
+static int dt282x_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dt282x_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int i;
+
+ devpriv->dacsr |= DT2821_DACSR_SSEL | DT2821_DACSR_YSEL(chan);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ if (comedi_range_is_bipolar(s, range))
+ val = comedi_offset_munge(s, val);
+
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
+
+ outw(val, dev->iobase + DT2821_DADAT_REG);
+
+ outw(devpriv->supcsr | DT2821_SUPCSR_DACON,
+ dev->iobase + DT2821_SUPCSR_REG);
+ }
+
+ return insn->n;
+}
+
+static int dt282x_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct dt282x_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 5000);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_EXT | TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->scan_begin_arg;
+ devpriv->divisor = dt282x_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int dt282x_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_src)
+ return -EINVAL;
+
+ if (!dt282x_ao_setup_dma(dev, s, 0))
+ return -EPIPE;
+
+ if (!dt282x_ao_setup_dma(dev, s, 1))
+ return -EPIPE;
+
+ outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
+ dev->iobase + DT2821_SUPCSR_REG);
+ s->async->inttrig = NULL;
+
+ return 1;
+}
+
+static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ dt282x_disable_dma(dev);
+
+ devpriv->supcsr = DT2821_SUPCSR_ERRINTEN |
+ DT2821_SUPCSR_DS_DA_CLK |
+ DT2821_SUPCSR_DDMA;
+ outw(devpriv->supcsr |
+ DT2821_SUPCSR_CLRDMADNE |
+ DT2821_SUPCSR_BUFFB |
+ DT2821_SUPCSR_DACINIT,
+ dev->iobase + DT2821_SUPCSR_REG);
+
+ devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len;
+ devpriv->nread = devpriv->ntrig;
+
+ devpriv->dma_dir = COMEDI_ISADMA_WRITE;
+ dma->cur_dma = 0;
+
+ outw(devpriv->divisor, dev->iobase + DT2821_TMRCTR_REG);
+
+ /* clear all bits but the DIO direction bits */
+ devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE);
+
+ devpriv->dacsr |= (DT2821_DACSR_SSEL |
+ DT2821_DACSR_DACLK |
+ DT2821_DACSR_IDARDY);
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
+
+ s->async->inttrig = dt282x_ao_inttrig;
+
+ return 0;
+}
+
+static int dt282x_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ dt282x_disable_dma(dev);
+
+ /* clear all bits but the DIO direction bits */
+ devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE);
+
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
+
+ devpriv->supcsr = 0;
+ outw(devpriv->supcsr | DT2821_SUPCSR_DACINIT,
+ dev->iobase + DT2821_SUPCSR_REG);
+
+ return 0;
+}
+
+static int dt282x_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + DT2821_DIODAT_REG);
+
+ data[1] = inw(dev->iobase + DT2821_DIODAT_REG);
+
+ return insn->n;
+}
+
+static int dt282x_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dt282x_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x00ff;
+ else
+ mask = 0xff00;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ devpriv->dacsr &= ~(DT2821_DACSR_LBOE | DT2821_DACSR_HBOE);
+ if (s->io_bits & 0x00ff)
+ devpriv->dacsr |= DT2821_DACSR_LBOE;
+ if (s->io_bits & 0xff00)
+ devpriv->dacsr |= DT2821_DACSR_HBOE;
+
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
+
+ return insn->n;
+}
+
+static const struct comedi_lrange *const ai_range_table[] = {
+ &range_dt282x_ai_lo_bipolar,
+ &range_dt282x_ai_lo_unipolar,
+ &range_dt282x_ai_5_bipolar,
+ &range_dt282x_ai_5_unipolar
+};
+
+static const struct comedi_lrange *const ai_range_pgl_table[] = {
+ &range_dt282x_ai_hi_bipolar,
+ &range_dt282x_ai_hi_unipolar
+};
+
+static const struct comedi_lrange *opt_ai_range_lkup(int ispgl, int x)
+{
+ if (ispgl) {
+ if (x < 0 || x >= 2)
+ x = 0;
+ return ai_range_pgl_table[x];
+ }
+
+ if (x < 0 || x >= 4)
+ x = 0;
+ return ai_range_table[x];
+}
+
+static void dt282x_alloc_dma(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct dt282x_private *devpriv = dev->private;
+ unsigned int irq_num = it->options[1];
+ unsigned int dma_chan[2];
+
+ if (it->options[2] < it->options[3]) {
+ dma_chan[0] = it->options[2];
+ dma_chan[1] = it->options[3];
+ } else {
+ dma_chan[0] = it->options[3];
+ dma_chan[1] = it->options[2];
+ }
+
+ if (!irq_num || dma_chan[0] == dma_chan[1] ||
+ dma_chan[0] < 5 || dma_chan[0] > 7 ||
+ dma_chan[1] < 5 || dma_chan[1] > 7)
+ return;
+
+ if (request_irq(irq_num, dt282x_interrupt, 0, dev->board_name, dev))
+ return;
+
+ /* DMA uses two 4K buffers with separate DMA channels */
+ devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan[0], dma_chan[1],
+ PAGE_SIZE, 0);
+ if (!devpriv->dma)
+ free_irq(irq_num, dev);
+}
+
+static void dt282x_free_dma(struct comedi_device *dev)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ if (devpriv)
+ comedi_isadma_free(devpriv->dma);
+}
+
+static int dt282x_initialize(struct comedi_device *dev)
+{
+ /* Initialize board */
+ outw(DT2821_SUPCSR_BDINIT, dev->iobase + DT2821_SUPCSR_REG);
+ inw(dev->iobase + DT2821_ADCSR_REG);
+
+ /*
+ * At power up, some registers are in a well-known state.
+ * Check them to see if a DT2821 series board is present.
+ */
+ if (((inw(dev->iobase + DT2821_ADCSR_REG) & 0xfff0) != 0x7c00) ||
+ ((inw(dev->iobase + DT2821_CHANCSR_REG) & 0xf0f0) != 0x70f0) ||
+ ((inw(dev->iobase + DT2821_DACSR_REG) & 0x7c93) != 0x7c90) ||
+ ((inw(dev->iobase + DT2821_SUPCSR_REG) & 0xf8ff) != 0x0000) ||
+ ((inw(dev->iobase + DT2821_TMRCTR_REG) & 0xff00) != 0xf000)) {
+ dev_err(dev->class_dev, "board not found\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+/*
+ options:
+ 0 i/o base
+ 1 irq
+ 2 dma1
+ 3 dma2
+ 4 0=single ended, 1=differential
+ 5 ai 0=straight binary, 1=2's comp
+ 6 ao0 0=straight binary, 1=2's comp
+ 7 ao1 0=straight binary, 1=2's comp
+ 8 ai 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V
+ 9 ao0 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
+ 10 ao1 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
+ */
+static int dt282x_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct dt282x_board *board = dev->board_ptr;
+ struct dt282x_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ ret = dt282x_initialize(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* an IRQ and 2 DMA channels are required for async command support */
+ dt282x_alloc_dma(dev, it);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if ((it->options[4] && board->adchan_di) || board->adchan_se == 0) {
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = board->adchan_di;
+ } else {
+ s->subdev_flags |= SDF_COMMON;
+ s->n_chan = board->adchan_se;
+ }
+ s->maxdata = board->ai_maxdata;
+
+ s->range_table = opt_ai_range_lkup(board->ispgl, it->options[8]);
+ devpriv->ad_2scomp = it->options[5] ? 1 : 0;
+
+ s->insn_read = dt282x_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = dt282x_ai_cmdtest;
+ s->do_cmd = dt282x_ai_cmd;
+ s->cancel = dt282x_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ if (board->dachan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->dachan;
+ s->maxdata = board->ao_maxdata;
+ /* ranges are per-channel, set by jumpers on the board */
+ s->range_table = &dt282x_ao_range;
+ s->insn_write = dt282x_ao_insn_write;
+ if (dev->irq) {
+ dev->write_subdev = s;
+ s->subdev_flags |= SDF_CMD_WRITE;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = dt282x_ao_cmdtest;
+ s->do_cmd = dt282x_ao_cmd;
+ s->cancel = dt282x_ao_cancel;
+ }
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dt282x_dio_insn_bits;
+ s->insn_config = dt282x_dio_insn_config;
+
+ return 0;
+}
+
+static void dt282x_detach(struct comedi_device *dev)
+{
+ dt282x_free_dma(dev);
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver dt282x_driver = {
+ .driver_name = "dt282x",
+ .module = THIS_MODULE,
+ .attach = dt282x_attach,
+ .detach = dt282x_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct dt282x_board),
+};
+module_comedi_driver(dt282x_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Data Translation DT2821 series");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt3000.c b/drivers/staging/comedi/drivers/dt3000.c
new file mode 100644
index 000000000..031282c82
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt3000.c
@@ -0,0 +1,769 @@
+/*
+ comedi/drivers/dt3000.c
+ Data Translation DT3000 series driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: dt3000
+Description: Data Translation DT3000 series
+Author: ds
+Devices: [Data Translation] DT3001 (dt3000), DT3001-PGL, DT3002, DT3003,
+ DT3003-PGL, DT3004, DT3005, DT3004-200
+Updated: Mon, 14 Apr 2008 15:41:24 +0100
+Status: works
+
+Configuration Options: not applicable, uses PCI auto config
+
+There is code to support AI commands, but it may not work.
+
+AO commands are not supported.
+*/
+
+/*
+ The DT3000 series is Data Translation's attempt to make a PCI
+ data acquisition board. The design of this series is very nice,
+ since each board has an on-board DSP (Texas Instruments TMS320C52).
+ However, a few details are a little annoying. The boards lack
+ bus-mastering DMA, which eliminates them from serious work.
+ They also are not capable of autocalibration, which is a common
+ feature in modern hardware. The default firmware is pretty bad,
+ making it nearly impossible to write an RT compatible driver.
+ It would make an interesting project to write a decent firmware
+ for these boards.
+
+ Data Translation originally wanted an NDA for the documentation
+ for the 3k series. However, if you ask nicely, they might send
+ you the docs without one, also.
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+static const struct comedi_lrange range_dt3000_ai = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_dt3000_ai_pgl = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+enum dt3k_boardid {
+ BOARD_DT3001,
+ BOARD_DT3001_PGL,
+ BOARD_DT3002,
+ BOARD_DT3003,
+ BOARD_DT3003_PGL,
+ BOARD_DT3004,
+ BOARD_DT3005,
+};
+
+struct dt3k_boardtype {
+ const char *name;
+ int adchan;
+ int adbits;
+ int ai_speed;
+ const struct comedi_lrange *adrange;
+ int dachan;
+ int dabits;
+};
+
+static const struct dt3k_boardtype dt3k_boardtypes[] = {
+ [BOARD_DT3001] = {
+ .name = "dt3001",
+ .adchan = 16,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3001_PGL] = {
+ .name = "dt3001-pgl",
+ .adchan = 16,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai_pgl,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3002] = {
+ .name = "dt3002",
+ .adchan = 32,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 3000,
+ },
+ [BOARD_DT3003] = {
+ .name = "dt3003",
+ .adchan = 64,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3003_PGL] = {
+ .name = "dt3003-pgl",
+ .adchan = 64,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai_pgl,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3004] = {
+ .name = "dt3004",
+ .adchan = 16,
+ .adbits = 16,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 10000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3005] = {
+ .name = "dt3005", /* a.k.a. 3004-200 */
+ .adchan = 16,
+ .adbits = 16,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 5000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+};
+
+/* dual-ported RAM location definitions */
+
+#define DPR_DAC_buffer (4*0x000)
+#define DPR_ADC_buffer (4*0x800)
+#define DPR_Command (4*0xfd3)
+#define DPR_SubSys (4*0xfd3)
+#define DPR_Encode (4*0xfd4)
+#define DPR_Params(a) (4*(0xfd5+(a)))
+#define DPR_Tick_Reg_Lo (4*0xff5)
+#define DPR_Tick_Reg_Hi (4*0xff6)
+#define DPR_DA_Buf_Front (4*0xff7)
+#define DPR_DA_Buf_Rear (4*0xff8)
+#define DPR_AD_Buf_Front (4*0xff9)
+#define DPR_AD_Buf_Rear (4*0xffa)
+#define DPR_Int_Mask (4*0xffb)
+#define DPR_Intr_Flag (4*0xffc)
+#define DPR_Response_Mbx (4*0xffe)
+#define DPR_Command_Mbx (4*0xfff)
+
+#define AI_FIFO_DEPTH 2003
+#define AO_FIFO_DEPTH 2048
+
+/* command list */
+
+#define CMD_GETBRDINFO 0
+#define CMD_CONFIG 1
+#define CMD_GETCONFIG 2
+#define CMD_START 3
+#define CMD_STOP 4
+#define CMD_READSINGLE 5
+#define CMD_WRITESINGLE 6
+#define CMD_CALCCLOCK 7
+#define CMD_READEVENTS 8
+#define CMD_WRITECTCTRL 16
+#define CMD_READCTCTRL 17
+#define CMD_WRITECT 18
+#define CMD_READCT 19
+#define CMD_WRITEDATA 32
+#define CMD_READDATA 33
+#define CMD_WRITEIO 34
+#define CMD_READIO 35
+#define CMD_WRITECODE 36
+#define CMD_READCODE 37
+#define CMD_EXECUTE 38
+#define CMD_HALT 48
+
+#define SUBS_AI 0
+#define SUBS_AO 1
+#define SUBS_DIN 2
+#define SUBS_DOUT 3
+#define SUBS_MEM 4
+#define SUBS_CT 5
+
+/* interrupt flags */
+#define DT3000_CMDONE 0x80
+#define DT3000_CTDONE 0x40
+#define DT3000_DAHWERR 0x20
+#define DT3000_DASWERR 0x10
+#define DT3000_DAEMPTY 0x08
+#define DT3000_ADHWERR 0x04
+#define DT3000_ADSWERR 0x02
+#define DT3000_ADFULL 0x01
+
+#define DT3000_COMPLETION_MASK 0xff00
+#define DT3000_COMMAND_MASK 0x00ff
+#define DT3000_NOTPROCESSED 0x0000
+#define DT3000_NOERROR 0x5500
+#define DT3000_ERROR 0xaa00
+#define DT3000_NOTSUPPORTED 0xff00
+
+#define DT3000_EXTERNAL_CLOCK 1
+#define DT3000_RISING_EDGE 2
+
+#define TMODE_MASK 0x1c
+
+#define DT3000_AD_TRIG_INTERNAL (0<<2)
+#define DT3000_AD_TRIG_EXTERNAL (1<<2)
+#define DT3000_AD_RETRIG_INTERNAL (2<<2)
+#define DT3000_AD_RETRIG_EXTERNAL (3<<2)
+#define DT3000_AD_EXTRETRIG (4<<2)
+
+#define DT3000_CHANNEL_MODE_SE 0
+#define DT3000_CHANNEL_MODE_DI 1
+
+struct dt3k_private {
+ unsigned int lock;
+ unsigned int ai_front;
+ unsigned int ai_rear;
+};
+
+#define TIMEOUT 100
+
+static void dt3k_send_cmd(struct comedi_device *dev, unsigned int cmd)
+{
+ int i;
+ unsigned int status = 0;
+
+ writew(cmd, dev->mmio + DPR_Command_Mbx);
+
+ for (i = 0; i < TIMEOUT; i++) {
+ status = readw(dev->mmio + DPR_Command_Mbx);
+ if ((status & DT3000_COMPLETION_MASK) != DT3000_NOTPROCESSED)
+ break;
+ udelay(1);
+ }
+
+ if ((status & DT3000_COMPLETION_MASK) != DT3000_NOERROR)
+ dev_dbg(dev->class_dev, "%s: timeout/error status=0x%04x\n",
+ __func__, status);
+}
+
+static unsigned int dt3k_readsingle(struct comedi_device *dev,
+ unsigned int subsys, unsigned int chan,
+ unsigned int gain)
+{
+ writew(subsys, dev->mmio + DPR_SubSys);
+
+ writew(chan, dev->mmio + DPR_Params(0));
+ writew(gain, dev->mmio + DPR_Params(1));
+
+ dt3k_send_cmd(dev, CMD_READSINGLE);
+
+ return readw(dev->mmio + DPR_Params(2));
+}
+
+static void dt3k_writesingle(struct comedi_device *dev, unsigned int subsys,
+ unsigned int chan, unsigned int data)
+{
+ writew(subsys, dev->mmio + DPR_SubSys);
+
+ writew(chan, dev->mmio + DPR_Params(0));
+ writew(0, dev->mmio + DPR_Params(1));
+ writew(data, dev->mmio + DPR_Params(2));
+
+ dt3k_send_cmd(dev, CMD_WRITESINGLE);
+}
+
+static void dt3k_ai_empty_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dt3k_private *devpriv = dev->private;
+ int front;
+ int rear;
+ int count;
+ int i;
+ unsigned short data;
+
+ front = readw(dev->mmio + DPR_AD_Buf_Front);
+ count = front - devpriv->ai_front;
+ if (count < 0)
+ count += AI_FIFO_DEPTH;
+
+ rear = devpriv->ai_rear;
+
+ for (i = 0; i < count; i++) {
+ data = readw(dev->mmio + DPR_ADC_buffer + rear);
+ comedi_buf_write_samples(s, &data, 1);
+ rear++;
+ if (rear >= AI_FIFO_DEPTH)
+ rear = 0;
+ }
+
+ devpriv->ai_rear = rear;
+ writew(rear, dev->mmio + DPR_AD_Buf_Rear);
+}
+
+static int dt3k_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ writew(SUBS_AI, dev->mmio + DPR_SubSys);
+ dt3k_send_cmd(dev, CMD_STOP);
+
+ writew(0, dev->mmio + DPR_Int_Mask);
+
+ return 0;
+}
+
+static int debug_n_ints;
+
+/* FIXME! Assumes shared interrupt is for this card. */
+/* What's this debug_n_ints stuff? Obviously needs some work... */
+static irqreturn_t dt3k_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ status = readw(dev->mmio + DPR_Intr_Flag);
+
+ if (status & DT3000_ADFULL)
+ dt3k_ai_empty_fifo(dev, s);
+
+ if (status & (DT3000_ADSWERR | DT3000_ADHWERR))
+ s->async->events |= COMEDI_CB_ERROR;
+
+ debug_n_ints++;
+ if (debug_n_ints >= 10)
+ s->async->events |= COMEDI_CB_EOA;
+
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *nanosec,
+ unsigned int flags)
+{
+ int divider, base, prescale;
+
+ /* This function needs improvment */
+ /* Don't know if divider==0 works. */
+
+ for (prescale = 0; prescale < 16; prescale++) {
+ base = timer_base * (prescale + 1);
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ divider = (*nanosec + base / 2) / base;
+ break;
+ case CMDF_ROUND_DOWN:
+ divider = (*nanosec) / base;
+ break;
+ case CMDF_ROUND_UP:
+ divider = (*nanosec) / base;
+ break;
+ }
+ if (divider < 65536) {
+ *nanosec = divider * base;
+ return (prescale << 16) | (divider);
+ }
+ }
+
+ prescale = 15;
+ base = timer_base * (1 << prescale);
+ divider = 65535;
+ *nanosec = divider * base;
+ return (prescale << 16) | (divider);
+}
+
+static int dt3k_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct dt3k_boardtype *this_board = dev->board_ptr;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ this_board->ai_speed);
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ 100 * 16 * 65535);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ this_board->ai_speed);
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg,
+ 50 * 16 * 65535);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ dt3k_ns_to_timer(100, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ dt3k_ns_to_timer(50, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int dt3k_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int i;
+ unsigned int chan, range, aref;
+ unsigned int divider;
+ unsigned int tscandiv;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ chan = CR_CHAN(cmd->chanlist[i]);
+ range = CR_RANGE(cmd->chanlist[i]);
+
+ writew((range << 6) | chan, dev->mmio + DPR_ADC_buffer + i);
+ }
+ aref = CR_AREF(cmd->chanlist[0]);
+
+ writew(cmd->scan_end_arg, dev->mmio + DPR_Params(0));
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ divider = dt3k_ns_to_timer(50, &cmd->convert_arg, cmd->flags);
+ writew((divider >> 16), dev->mmio + DPR_Params(1));
+ writew((divider & 0xffff), dev->mmio + DPR_Params(2));
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ tscandiv = dt3k_ns_to_timer(100, &cmd->scan_begin_arg,
+ cmd->flags);
+ writew((tscandiv >> 16), dev->mmio + DPR_Params(3));
+ writew((tscandiv & 0xffff), dev->mmio + DPR_Params(4));
+ }
+
+ writew(DT3000_AD_RETRIG_INTERNAL, dev->mmio + DPR_Params(5));
+ writew(aref == AREF_DIFF, dev->mmio + DPR_Params(6));
+
+ writew(AI_FIFO_DEPTH / 2, dev->mmio + DPR_Params(7));
+
+ writew(SUBS_AI, dev->mmio + DPR_SubSys);
+ dt3k_send_cmd(dev, CMD_CONFIG);
+
+ writew(DT3000_ADFULL | DT3000_ADSWERR | DT3000_ADHWERR,
+ dev->mmio + DPR_Int_Mask);
+
+ debug_n_ints = 0;
+
+ writew(SUBS_AI, dev->mmio + DPR_SubSys);
+ dt3k_send_cmd(dev, CMD_START);
+
+ return 0;
+}
+
+static int dt3k_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int i;
+ unsigned int chan, gain, aref;
+
+ chan = CR_CHAN(insn->chanspec);
+ gain = CR_RANGE(insn->chanspec);
+ /* XXX docs don't explain how to select aref */
+ aref = CR_AREF(insn->chanspec);
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = dt3k_readsingle(dev, SUBS_AI, chan, gain);
+
+ return i;
+}
+
+static int dt3k_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ dt3k_writesingle(dev, SUBS_AO, chan, val);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static void dt3k_dio_config(struct comedi_device *dev, int bits)
+{
+ /* XXX */
+ writew(SUBS_DOUT, dev->mmio + DPR_SubSys);
+
+ writew(bits, dev->mmio + DPR_Params(0));
+#if 0
+ /* don't know */
+ writew(0, dev->mmio + DPR_Params(1));
+ writew(0, dev->mmio + DPR_Params(2));
+#endif
+
+ dt3k_send_cmd(dev, CMD_CONFIG);
+}
+
+static int dt3k_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ dt3k_dio_config(dev, (s->io_bits & 0x01) | ((s->io_bits & 0x10) >> 3));
+
+ return insn->n;
+}
+
+static int dt3k_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ dt3k_writesingle(dev, SUBS_DOUT, 0, s->state);
+
+ data[1] = dt3k_readsingle(dev, SUBS_DIN, 0, 0);
+
+ return insn->n;
+}
+
+static int dt3k_mem_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int addr = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ writew(SUBS_MEM, dev->mmio + DPR_SubSys);
+ writew(addr, dev->mmio + DPR_Params(0));
+ writew(1, dev->mmio + DPR_Params(1));
+
+ dt3k_send_cmd(dev, CMD_READCODE);
+
+ data[i] = readw(dev->mmio + DPR_Params(2));
+ }
+
+ return i;
+}
+
+static int dt3000_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct dt3k_boardtype *this_board = NULL;
+ struct dt3k_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret = 0;
+
+ if (context < ARRAY_SIZE(dt3k_boardtypes))
+ this_board = &dt3k_boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret < 0)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 0);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, dt3k_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = this_board->adchan;
+ s->insn_read = dt3k_ai_insn;
+ s->maxdata = (1 << this_board->adbits) - 1;
+ s->range_table = &range_dt3000_ai; /* XXX */
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 512;
+ s->do_cmd = dt3k_ai_cmd;
+ s->do_cmdtest = dt3k_ai_cmdtest;
+ s->cancel = dt3k_ai_cancel;
+ }
+
+ s = &dev->subdevices[1];
+ /* ao subsystem */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = (1 << this_board->dabits) - 1;
+ s->len_chanlist = 1;
+ s->range_table = &range_bipolar10;
+ s->insn_write = dt3k_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ /* dio subsystem */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->insn_config = dt3k_dio_insn_config;
+ s->insn_bits = dt3k_dio_insn_bits;
+ s->maxdata = 1;
+ s->len_chanlist = 8;
+ s->range_table = &range_digital;
+
+ s = &dev->subdevices[3];
+ /* mem subsystem */
+ s->type = COMEDI_SUBD_MEMORY;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 0x1000;
+ s->insn_read = dt3k_mem_insn_read;
+ s->maxdata = 0xff;
+ s->len_chanlist = 1;
+ s->range_table = &range_unknown;
+
+#if 0
+ s = &dev->subdevices[4];
+ /* proc subsystem */
+ s->type = COMEDI_SUBD_PROC;
+#endif
+
+ return 0;
+}
+
+static struct comedi_driver dt3000_driver = {
+ .driver_name = "dt3000",
+ .module = THIS_MODULE,
+ .auto_attach = dt3000_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int dt3000_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &dt3000_driver, id->driver_data);
+}
+
+static const struct pci_device_id dt3000_pci_table[] = {
+ { PCI_VDEVICE(DT, 0x0022), BOARD_DT3001 },
+ { PCI_VDEVICE(DT, 0x0023), BOARD_DT3002 },
+ { PCI_VDEVICE(DT, 0x0024), BOARD_DT3003 },
+ { PCI_VDEVICE(DT, 0x0025), BOARD_DT3004 },
+ { PCI_VDEVICE(DT, 0x0026), BOARD_DT3005 },
+ { PCI_VDEVICE(DT, 0x0027), BOARD_DT3001_PGL },
+ { PCI_VDEVICE(DT, 0x0028), BOARD_DT3003_PGL },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, dt3000_pci_table);
+
+static struct pci_driver dt3000_pci_driver = {
+ .name = "dt3000",
+ .id_table = dt3000_pci_table,
+ .probe = dt3000_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(dt3000_driver, dt3000_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dt9812.c b/drivers/staging/comedi/drivers/dt9812.c
new file mode 100644
index 000000000..e11c216a4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt9812.c
@@ -0,0 +1,885 @@
+/*
+ * comedi/drivers/dt9812.c
+ * COMEDI driver for DataTranslation DT9812 USB module
+ *
+ * Copyright (C) 2005 Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+Driver: dt9812
+Description: Data Translation DT9812 USB module
+Author: anders.blomdell@control.lth.se (Anders Blomdell)
+Status: in development
+Devices: [Data Translation] DT9812 (dt9812)
+Updated: Sun Nov 20 20:18:34 EST 2005
+
+This driver works, but bulk transfers not implemented. Might be a starting point
+for someone else. I found out too late that USB has too high latencies (>1 ms)
+for my needs.
+*/
+
+/*
+ * Nota Bene:
+ * 1. All writes to command pipe has to be 32 bytes (ISP1181B SHRTP=0 ?)
+ * 2. The DDK source (as of sep 2005) is in error regarding the
+ * input MUX bits (example code says P4, but firmware schematics
+ * says P1).
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+
+#include "../comedi_usb.h"
+
+#define DT9812_DIAGS_BOARD_INFO_ADDR 0xFBFF
+#define DT9812_MAX_WRITE_CMD_PIPE_SIZE 32
+#define DT9812_MAX_READ_CMD_PIPE_SIZE 32
+
+/* usb_bulk_msg() timout in milliseconds */
+#define DT9812_USB_TIMEOUT 1000
+
+/*
+ * See Silican Laboratories C8051F020/1/2/3 manual
+ */
+#define F020_SFR_P4 0x84
+#define F020_SFR_P1 0x90
+#define F020_SFR_P2 0xa0
+#define F020_SFR_P3 0xb0
+#define F020_SFR_AMX0CF 0xba
+#define F020_SFR_AMX0SL 0xbb
+#define F020_SFR_ADC0CF 0xbc
+#define F020_SFR_ADC0L 0xbe
+#define F020_SFR_ADC0H 0xbf
+#define F020_SFR_DAC0L 0xd2
+#define F020_SFR_DAC0H 0xd3
+#define F020_SFR_DAC0CN 0xd4
+#define F020_SFR_DAC1L 0xd5
+#define F020_SFR_DAC1H 0xd6
+#define F020_SFR_DAC1CN 0xd7
+#define F020_SFR_ADC0CN 0xe8
+
+#define F020_MASK_ADC0CF_AMP0GN0 0x01
+#define F020_MASK_ADC0CF_AMP0GN1 0x02
+#define F020_MASK_ADC0CF_AMP0GN2 0x04
+
+#define F020_MASK_ADC0CN_AD0EN 0x80
+#define F020_MASK_ADC0CN_AD0INT 0x20
+#define F020_MASK_ADC0CN_AD0BUSY 0x10
+
+#define F020_MASK_DACxCN_DACxEN 0x80
+
+enum {
+ /* A/D D/A DI DO CT */
+ DT9812_DEVID_DT9812_10, /* 8 2 8 8 1 +/- 10V */
+ DT9812_DEVID_DT9812_2PT5, /* 8 2 8 8 1 0-2.44V */
+};
+
+enum dt9812_gain {
+ DT9812_GAIN_0PT25 = 1,
+ DT9812_GAIN_0PT5 = 2,
+ DT9812_GAIN_1 = 4,
+ DT9812_GAIN_2 = 8,
+ DT9812_GAIN_4 = 16,
+ DT9812_GAIN_8 = 32,
+ DT9812_GAIN_16 = 64,
+};
+
+enum {
+ DT9812_LEAST_USB_FIRMWARE_CMD_CODE = 0,
+ /* Write Flash memory */
+ DT9812_W_FLASH_DATA = 0,
+ /* Read Flash memory misc config info */
+ DT9812_R_FLASH_DATA = 1,
+
+ /*
+ * Register read/write commands for processor
+ */
+
+ /* Read a single byte of USB memory */
+ DT9812_R_SINGLE_BYTE_REG = 2,
+ /* Write a single byte of USB memory */
+ DT9812_W_SINGLE_BYTE_REG = 3,
+ /* Multiple Reads of USB memory */
+ DT9812_R_MULTI_BYTE_REG = 4,
+ /* Multiple Writes of USB memory */
+ DT9812_W_MULTI_BYTE_REG = 5,
+ /* Read, (AND) with mask, OR value, then write (single) */
+ DT9812_RMW_SINGLE_BYTE_REG = 6,
+ /* Read, (AND) with mask, OR value, then write (multiple) */
+ DT9812_RMW_MULTI_BYTE_REG = 7,
+
+ /*
+ * Register read/write commands for SMBus
+ */
+
+ /* Read a single byte of SMBus */
+ DT9812_R_SINGLE_BYTE_SMBUS = 8,
+ /* Write a single byte of SMBus */
+ DT9812_W_SINGLE_BYTE_SMBUS = 9,
+ /* Multiple Reads of SMBus */
+ DT9812_R_MULTI_BYTE_SMBUS = 10,
+ /* Multiple Writes of SMBus */
+ DT9812_W_MULTI_BYTE_SMBUS = 11,
+
+ /*
+ * Register read/write commands for a device
+ */
+
+ /* Read a single byte of a device */
+ DT9812_R_SINGLE_BYTE_DEV = 12,
+ /* Write a single byte of a device */
+ DT9812_W_SINGLE_BYTE_DEV = 13,
+ /* Multiple Reads of a device */
+ DT9812_R_MULTI_BYTE_DEV = 14,
+ /* Multiple Writes of a device */
+ DT9812_W_MULTI_BYTE_DEV = 15,
+
+ /* Not sure if we'll need this */
+ DT9812_W_DAC_THRESHOLD = 16,
+
+ /* Set interrupt on change mask */
+ DT9812_W_INT_ON_CHANGE_MASK = 17,
+
+ /* Write (or Clear) the CGL for the ADC */
+ DT9812_W_CGL = 18,
+ /* Multiple Reads of USB memory */
+ DT9812_R_MULTI_BYTE_USBMEM = 19,
+ /* Multiple Writes to USB memory */
+ DT9812_W_MULTI_BYTE_USBMEM = 20,
+
+ /* Issue a start command to a given subsystem */
+ DT9812_START_SUBSYSTEM = 21,
+ /* Issue a stop command to a given subsystem */
+ DT9812_STOP_SUBSYSTEM = 22,
+
+ /* calibrate the board using CAL_POT_CMD */
+ DT9812_CALIBRATE_POT = 23,
+ /* set the DAC FIFO size */
+ DT9812_W_DAC_FIFO_SIZE = 24,
+ /* Write or Clear the CGL for the DAC */
+ DT9812_W_CGL_DAC = 25,
+ /* Read a single value from a subsystem */
+ DT9812_R_SINGLE_VALUE_CMD = 26,
+ /* Write a single value to a subsystem */
+ DT9812_W_SINGLE_VALUE_CMD = 27,
+ /* Valid DT9812_USB_FIRMWARE_CMD_CODE's will be less than this number */
+ DT9812_MAX_USB_FIRMWARE_CMD_CODE,
+};
+
+struct dt9812_flash_data {
+ __le16 numbytes;
+ __le16 address;
+};
+
+#define DT9812_MAX_NUM_MULTI_BYTE_RDS \
+ ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(u8))
+
+struct dt9812_read_multi {
+ u8 count;
+ u8 address[DT9812_MAX_NUM_MULTI_BYTE_RDS];
+};
+
+struct dt9812_write_byte {
+ u8 address;
+ u8 value;
+};
+
+#define DT9812_MAX_NUM_MULTI_BYTE_WRTS \
+ ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / \
+ sizeof(struct dt9812_write_byte))
+
+struct dt9812_write_multi {
+ u8 count;
+ struct dt9812_write_byte write[DT9812_MAX_NUM_MULTI_BYTE_WRTS];
+};
+
+struct dt9812_rmw_byte {
+ u8 address;
+ u8 and_mask;
+ u8 or_value;
+};
+
+#define DT9812_MAX_NUM_MULTI_BYTE_RMWS \
+ ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / \
+ sizeof(struct dt9812_rmw_byte))
+
+struct dt9812_rmw_multi {
+ u8 count;
+ struct dt9812_rmw_byte rmw[DT9812_MAX_NUM_MULTI_BYTE_RMWS];
+};
+
+struct dt9812_usb_cmd {
+ __le32 cmd;
+ union {
+ struct dt9812_flash_data flash_data_info;
+ struct dt9812_read_multi read_multi_info;
+ struct dt9812_write_multi write_multi_info;
+ struct dt9812_rmw_multi rmw_multi_info;
+ } u;
+};
+
+struct dt9812_private {
+ struct semaphore sem;
+ struct {
+ __u8 addr;
+ size_t size;
+ } cmd_wr, cmd_rd;
+ u16 device;
+};
+
+static int dt9812_read_info(struct comedi_device *dev,
+ int offset, void *buf, size_t buf_size)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int count, ret;
+
+ cmd.cmd = cpu_to_le32(DT9812_R_FLASH_DATA);
+ cmd.u.flash_data_info.address =
+ cpu_to_le16(DT9812_DIAGS_BOARD_INFO_ADDR + offset);
+ cmd.u.flash_data_info.numbytes = cpu_to_le16(buf_size);
+
+ /* DT9812 only responds to 32 byte writes!! */
+ ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr),
+ buf, buf_size, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_read_multiple_registers(struct comedi_device *dev,
+ int reg_count, u8 *address,
+ u8 *value)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int i, count, ret;
+
+ cmd.cmd = cpu_to_le32(DT9812_R_MULTI_BYTE_REG);
+ cmd.u.read_multi_info.count = reg_count;
+ for (i = 0; i < reg_count; i++)
+ cmd.u.read_multi_info.address[i] = address[i];
+
+ /* DT9812 only responds to 32 byte writes!! */
+ ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr),
+ value, reg_count, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_write_multiple_registers(struct comedi_device *dev,
+ int reg_count, u8 *address,
+ u8 *value)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int i, count;
+
+ cmd.cmd = cpu_to_le32(DT9812_W_MULTI_BYTE_REG);
+ cmd.u.read_multi_info.count = reg_count;
+ for (i = 0; i < reg_count; i++) {
+ cmd.u.write_multi_info.write[i].address = address[i];
+ cmd.u.write_multi_info.write[i].value = value[i];
+ }
+
+ /* DT9812 only responds to 32 byte writes!! */
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_rmw_multiple_registers(struct comedi_device *dev,
+ int reg_count,
+ struct dt9812_rmw_byte *rmw)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int i, count;
+
+ cmd.cmd = cpu_to_le32(DT9812_RMW_MULTI_BYTE_REG);
+ cmd.u.rmw_multi_info.count = reg_count;
+ for (i = 0; i < reg_count; i++)
+ cmd.u.rmw_multi_info.rmw[i] = rmw[i];
+
+ /* DT9812 only responds to 32 byte writes!! */
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_digital_in(struct comedi_device *dev, u8 *bits)
+{
+ struct dt9812_private *devpriv = dev->private;
+ u8 reg[2] = { F020_SFR_P3, F020_SFR_P1 };
+ u8 value[2];
+ int ret;
+
+ down(&devpriv->sem);
+ ret = dt9812_read_multiple_registers(dev, 2, reg, value);
+ if (ret == 0) {
+ /*
+ * bits 0-6 in F020_SFR_P3 are bits 0-6 in the digital
+ * input port bit 3 in F020_SFR_P1 is bit 7 in the
+ * digital input port
+ */
+ *bits = (value[0] & 0x7f) | ((value[1] & 0x08) << 4);
+ }
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int dt9812_digital_out(struct comedi_device *dev, u8 bits)
+{
+ struct dt9812_private *devpriv = dev->private;
+ u8 reg[1] = { F020_SFR_P2 };
+ u8 value[1] = { bits };
+ int ret;
+
+ down(&devpriv->sem);
+ ret = dt9812_write_multiple_registers(dev, 1, reg, value);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void dt9812_configure_mux(struct comedi_device *dev,
+ struct dt9812_rmw_byte *rmw, int channel)
+{
+ struct dt9812_private *devpriv = dev->private;
+
+ if (devpriv->device == DT9812_DEVID_DT9812_10) {
+ /* In the DT9812/10V MUX is selected by P1.5-7 */
+ rmw->address = F020_SFR_P1;
+ rmw->and_mask = 0xe0;
+ rmw->or_value = channel << 5;
+ } else {
+ /* In the DT9812/2.5V, internal mux is selected by bits 0:2 */
+ rmw->address = F020_SFR_AMX0SL;
+ rmw->and_mask = 0xff;
+ rmw->or_value = channel & 0x07;
+ }
+}
+
+static void dt9812_configure_gain(struct comedi_device *dev,
+ struct dt9812_rmw_byte *rmw,
+ enum dt9812_gain gain)
+{
+ struct dt9812_private *devpriv = dev->private;
+
+ /* In the DT9812/10V, there is an external gain of 0.5 */
+ if (devpriv->device == DT9812_DEVID_DT9812_10)
+ gain <<= 1;
+
+ rmw->address = F020_SFR_ADC0CF;
+ rmw->and_mask = F020_MASK_ADC0CF_AMP0GN2 |
+ F020_MASK_ADC0CF_AMP0GN1 |
+ F020_MASK_ADC0CF_AMP0GN0;
+
+ switch (gain) {
+ /*
+ * 000 -> Gain = 1
+ * 001 -> Gain = 2
+ * 010 -> Gain = 4
+ * 011 -> Gain = 8
+ * 10x -> Gain = 16
+ * 11x -> Gain = 0.5
+ */
+ case DT9812_GAIN_0PT5:
+ rmw->or_value = F020_MASK_ADC0CF_AMP0GN2 |
+ F020_MASK_ADC0CF_AMP0GN1;
+ break;
+ default:
+ /* this should never happen, just use a gain of 1 */
+ case DT9812_GAIN_1:
+ rmw->or_value = 0x00;
+ break;
+ case DT9812_GAIN_2:
+ rmw->or_value = F020_MASK_ADC0CF_AMP0GN0;
+ break;
+ case DT9812_GAIN_4:
+ rmw->or_value = F020_MASK_ADC0CF_AMP0GN1;
+ break;
+ case DT9812_GAIN_8:
+ rmw->or_value = F020_MASK_ADC0CF_AMP0GN1 |
+ F020_MASK_ADC0CF_AMP0GN0;
+ break;
+ case DT9812_GAIN_16:
+ rmw->or_value = F020_MASK_ADC0CF_AMP0GN2;
+ break;
+ }
+}
+
+static int dt9812_analog_in(struct comedi_device *dev,
+ int channel, u16 *value, enum dt9812_gain gain)
+{
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_rmw_byte rmw[3];
+ u8 reg[3] = {
+ F020_SFR_ADC0CN,
+ F020_SFR_ADC0H,
+ F020_SFR_ADC0L
+ };
+ u8 val[3];
+ int ret;
+
+ down(&devpriv->sem);
+
+ /* 1 select the gain */
+ dt9812_configure_gain(dev, &rmw[0], gain);
+
+ /* 2 set the MUX to select the channel */
+ dt9812_configure_mux(dev, &rmw[1], channel);
+
+ /* 3 start conversion */
+ rmw[2].address = F020_SFR_ADC0CN;
+ rmw[2].and_mask = 0xff;
+ rmw[2].or_value = F020_MASK_ADC0CN_AD0EN | F020_MASK_ADC0CN_AD0BUSY;
+
+ ret = dt9812_rmw_multiple_registers(dev, 3, rmw);
+ if (ret)
+ goto exit;
+
+ /* read the status and ADC */
+ ret = dt9812_read_multiple_registers(dev, 3, reg, val);
+ if (ret)
+ goto exit;
+
+ /*
+ * An ADC conversion takes 16 SAR clocks cycles, i.e. about 9us.
+ * Therefore, between the instant that AD0BUSY was set via
+ * dt9812_rmw_multiple_registers and the read of AD0BUSY via
+ * dt9812_read_multiple_registers, the conversion should be complete
+ * since these two operations require two USB transactions each taking
+ * at least a millisecond to complete. However, lets make sure that
+ * conversion is finished.
+ */
+ if ((val[0] & (F020_MASK_ADC0CN_AD0INT | F020_MASK_ADC0CN_AD0BUSY)) ==
+ F020_MASK_ADC0CN_AD0INT) {
+ switch (devpriv->device) {
+ case DT9812_DEVID_DT9812_10:
+ /*
+ * For DT9812-10V the personality module set the
+ * encoding to 2's complement. Hence, convert it before
+ * returning it
+ */
+ *value = ((val[1] << 8) | val[2]) + 0x800;
+ break;
+ case DT9812_DEVID_DT9812_2PT5:
+ *value = (val[1] << 8) | val[2];
+ break;
+ }
+ }
+
+exit:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int dt9812_analog_out(struct comedi_device *dev, int channel, u16 value)
+{
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_rmw_byte rmw[3];
+ int ret;
+
+ down(&devpriv->sem);
+
+ switch (channel) {
+ case 0:
+ /* 1. Set DAC mode */
+ rmw[0].address = F020_SFR_DAC0CN;
+ rmw[0].and_mask = 0xff;
+ rmw[0].or_value = F020_MASK_DACxCN_DACxEN;
+
+ /* 2 load low byte of DAC value first */
+ rmw[1].address = F020_SFR_DAC0L;
+ rmw[1].and_mask = 0xff;
+ rmw[1].or_value = value & 0xff;
+
+ /* 3 load high byte of DAC value next to latch the
+ 12-bit value */
+ rmw[2].address = F020_SFR_DAC0H;
+ rmw[2].and_mask = 0xff;
+ rmw[2].or_value = (value >> 8) & 0xf;
+ break;
+
+ case 1:
+ /* 1. Set DAC mode */
+ rmw[0].address = F020_SFR_DAC1CN;
+ rmw[0].and_mask = 0xff;
+ rmw[0].or_value = F020_MASK_DACxCN_DACxEN;
+
+ /* 2 load low byte of DAC value first */
+ rmw[1].address = F020_SFR_DAC1L;
+ rmw[1].and_mask = 0xff;
+ rmw[1].or_value = value & 0xff;
+
+ /* 3 load high byte of DAC value next to latch the
+ 12-bit value */
+ rmw[2].address = F020_SFR_DAC1H;
+ rmw[2].and_mask = 0xff;
+ rmw[2].or_value = (value >> 8) & 0xf;
+ break;
+ }
+ ret = dt9812_rmw_multiple_registers(dev, 3, rmw);
+
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int dt9812_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ u8 bits = 0;
+ int ret;
+
+ ret = dt9812_digital_in(dev, &bits);
+ if (ret)
+ return ret;
+
+ data[1] = bits;
+
+ return insn->n;
+}
+
+static int dt9812_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ dt9812_digital_out(dev, s->state);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int dt9812_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ u16 val = 0;
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ ret = dt9812_analog_in(dev, chan, &val, DT9812_GAIN_1);
+ if (ret)
+ return ret;
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int dt9812_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dt9812_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+ ret = comedi_readback_insn_read(dev, s, insn, data);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int dt9812_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+ int ret;
+
+ ret = dt9812_analog_out(dev, chan, val);
+ if (ret)
+ return ret;
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int dt9812_find_endpoints(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_host_interface *host = intf->cur_altsetting;
+ struct dt9812_private *devpriv = dev->private;
+ struct usb_endpoint_descriptor *ep;
+ int i;
+
+ if (host->desc.bNumEndpoints != 5) {
+ dev_err(dev->class_dev, "Wrong number of endpoints\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < host->desc.bNumEndpoints; ++i) {
+ int dir = -1;
+
+ ep = &host->endpoint[i].desc;
+ switch (i) {
+ case 0:
+ /* unused message pipe */
+ dir = USB_DIR_IN;
+ break;
+ case 1:
+ dir = USB_DIR_OUT;
+ devpriv->cmd_wr.addr = ep->bEndpointAddress;
+ devpriv->cmd_wr.size = le16_to_cpu(ep->wMaxPacketSize);
+ break;
+ case 2:
+ dir = USB_DIR_IN;
+ devpriv->cmd_rd.addr = ep->bEndpointAddress;
+ devpriv->cmd_rd.size = le16_to_cpu(ep->wMaxPacketSize);
+ break;
+ case 3:
+ /* unused write stream */
+ dir = USB_DIR_OUT;
+ break;
+ case 4:
+ /* unused read stream */
+ dir = USB_DIR_IN;
+ break;
+ }
+ if ((ep->bEndpointAddress & USB_DIR_IN) != dir) {
+ dev_err(dev->class_dev,
+ "Endpoint has wrong direction\n");
+ return -ENODEV;
+ }
+ }
+ return 0;
+}
+
+static int dt9812_reset_device(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ u32 serial;
+ u16 vendor;
+ u16 product;
+ u8 tmp8;
+ __le16 tmp16;
+ __le32 tmp32;
+ int ret;
+ int i;
+
+ ret = dt9812_read_info(dev, 0, &tmp8, sizeof(tmp8));
+ if (ret) {
+ /*
+ * Seems like a configuration reset is necessary if driver is
+ * reloaded while device is attached
+ */
+ usb_reset_configuration(usb);
+ for (i = 0; i < 10; i++) {
+ ret = dt9812_read_info(dev, 1, &tmp8, sizeof(tmp8));
+ if (ret == 0)
+ break;
+ }
+ if (ret) {
+ dev_err(dev->class_dev,
+ "unable to reset configuration\n");
+ return ret;
+ }
+ }
+
+ ret = dt9812_read_info(dev, 1, &tmp16, sizeof(tmp16));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read vendor id\n");
+ return ret;
+ }
+ vendor = le16_to_cpu(tmp16);
+
+ ret = dt9812_read_info(dev, 3, &tmp16, sizeof(tmp16));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read product id\n");
+ return ret;
+ }
+ product = le16_to_cpu(tmp16);
+
+ ret = dt9812_read_info(dev, 5, &tmp16, sizeof(tmp16));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read device id\n");
+ return ret;
+ }
+ devpriv->device = le16_to_cpu(tmp16);
+
+ ret = dt9812_read_info(dev, 7, &tmp32, sizeof(tmp32));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read serial number\n");
+ return ret;
+ }
+ serial = le32_to_cpu(tmp32);
+
+ /* let the user know what node this device is now attached to */
+ dev_info(dev->class_dev, "USB DT9812 (%4.4x.%4.4x.%4.4x) #0x%8.8x\n",
+ vendor, product, devpriv->device, serial);
+
+ if (devpriv->device != DT9812_DEVID_DT9812_10 &&
+ devpriv->device != DT9812_DEVID_DT9812_2PT5) {
+ dev_err(dev->class_dev, "Unsupported device!\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dt9812_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct dt9812_private *devpriv;
+ struct comedi_subdevice *s;
+ bool is_unipolar;
+ int ret;
+ int i;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+ usb_set_intfdata(intf, devpriv);
+
+ ret = dt9812_find_endpoints(dev);
+ if (ret)
+ return ret;
+
+ ret = dt9812_reset_device(dev);
+ if (ret)
+ return ret;
+
+ is_unipolar = (devpriv->device == DT9812_DEVID_DT9812_2PT5);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dt9812_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dt9812_do_insn_bits;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->maxdata = 0x0fff;
+ s->range_table = is_unipolar ? &range_unipolar2_5 : &range_bipolar10;
+ s->insn_read = dt9812_ai_insn_read;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = is_unipolar ? &range_unipolar2_5 : &range_bipolar10;
+ s->insn_write = dt9812_ao_insn_write;
+ s->insn_read = dt9812_ao_insn_read;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++)
+ s->readback[i] = is_unipolar ? 0x0000 : 0x0800;
+
+ return 0;
+}
+
+static void dt9812_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct dt9812_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ usb_set_intfdata(intf, NULL);
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver dt9812_driver = {
+ .driver_name = "dt9812",
+ .module = THIS_MODULE,
+ .auto_attach = dt9812_auto_attach,
+ .detach = dt9812_detach,
+};
+
+static int dt9812_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &dt9812_driver, id->driver_info);
+}
+
+static const struct usb_device_id dt9812_usb_table[] = {
+ { USB_DEVICE(0x0867, 0x9812) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, dt9812_usb_table);
+
+static struct usb_driver dt9812_usb_driver = {
+ .name = "dt9812",
+ .id_table = dt9812_usb_table,
+ .probe = dt9812_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+};
+module_comedi_usb_driver(dt9812_driver, dt9812_usb_driver);
+
+MODULE_AUTHOR("Anders Blomdell <anders.blomdell@control.lth.se>");
+MODULE_DESCRIPTION("Comedi DT9812 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dyna_pci10xx.c b/drivers/staging/comedi/drivers/dyna_pci10xx.c
new file mode 100644
index 000000000..c9eb26fab
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dyna_pci10xx.c
@@ -0,0 +1,282 @@
+/*
+ * comedi/drivers/dyna_pci10xx.c
+ * Copyright (C) 2011 Prashant Shah, pshah.mumbai@gmail.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: dyna_pci10xx
+ * Description: Dynalog India PCI DAQ Cards, http://www.dynalogindia.com/
+ * Devices: [Dynalog] PCI-1050 (dyna_pci1050)
+ * Author: Prashant Shah <pshah.mumbai@gmail.com>
+ * Status: Stable
+ *
+ * Developed at Automation Labs, Chemical Dept., IIT Bombay, India.
+ * Prof. Kannan Moudgalya <kannan@iitb.ac.in>
+ * http://www.iitb.ac.in
+ *
+ * Notes :
+ * - Dynalog India Pvt. Ltd. does not have a registered PCI Vendor ID and
+ * they are using the PLX Technlogies Vendor ID since that is the PCI Chip
+ * used in the card.
+ * - Dynalog India Pvt. Ltd. has provided the internal register specification
+ * for their cards in their manuals.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+
+#include "../comedi_pci.h"
+
+#define READ_TIMEOUT 50
+
+static const struct comedi_lrange range_pci1050_ai = {
+ 3, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+static const char range_codes_pci1050_ai[] = { 0x00, 0x10, 0x30 };
+
+struct dyna_pci10xx_private {
+ struct mutex mutex;
+ unsigned long BADR3;
+};
+
+static int dyna_pci10xx_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw_p(dev->iobase);
+ if (status & (1 << 15))
+ return 0;
+ return -EBUSY;
+}
+
+static int dyna_pci10xx_insn_read_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+ int n;
+ u16 d = 0;
+ int ret = 0;
+ unsigned int chan, range;
+
+ /* get the channel number and range */
+ chan = CR_CHAN(insn->chanspec);
+ range = range_codes_pci1050_ai[CR_RANGE((insn->chanspec))];
+
+ mutex_lock(&devpriv->mutex);
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion */
+ smp_mb();
+ outw_p(0x0000 + range + chan, dev->iobase + 2);
+ udelay(10);
+
+ ret = comedi_timeout(dev, s, insn, dyna_pci10xx_ai_eoc, 0);
+ if (ret)
+ break;
+
+ /* read data */
+ d = inw_p(dev->iobase);
+ /* mask the first 4 bits - EOC bits */
+ d &= 0x0FFF;
+ data[n] = d;
+ }
+ mutex_unlock(&devpriv->mutex);
+
+ /* return the number of samples read/written */
+ return ret ? ret : n;
+}
+
+/* analog output callback */
+static int dyna_pci10xx_insn_write_ao(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+ int n;
+ unsigned int chan, range;
+
+ chan = CR_CHAN(insn->chanspec);
+ range = range_codes_pci1050_ai[CR_RANGE((insn->chanspec))];
+
+ mutex_lock(&devpriv->mutex);
+ for (n = 0; n < insn->n; n++) {
+ smp_mb();
+ /* trigger conversion and write data */
+ outw_p(data[n], dev->iobase);
+ udelay(10);
+ }
+ mutex_unlock(&devpriv->mutex);
+ return n;
+}
+
+/* digital input bit interface */
+static int dyna_pci10xx_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+ u16 d = 0;
+
+ mutex_lock(&devpriv->mutex);
+ smp_mb();
+ d = inw_p(devpriv->BADR3);
+ udelay(10);
+
+ /* on return the data[0] contains output and data[1] contains input */
+ data[1] = d;
+ data[0] = s->state;
+ mutex_unlock(&devpriv->mutex);
+ return insn->n;
+}
+
+static int dyna_pci10xx_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+
+ mutex_lock(&devpriv->mutex);
+ if (comedi_dio_update_state(s, data)) {
+ smp_mb();
+ outw_p(s->state, devpriv->BADR3);
+ udelay(10);
+ }
+
+ data[1] = s->state;
+ mutex_unlock(&devpriv->mutex);
+
+ return insn->n;
+}
+
+static int dyna_pci10xx_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct dyna_pci10xx_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+ devpriv->BADR3 = pci_resource_start(pcidev, 3);
+
+ mutex_init(&devpriv->mutex);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* analog input */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0x0FFF;
+ s->range_table = &range_pci1050_ai;
+ s->len_chanlist = 16;
+ s->insn_read = dyna_pci10xx_insn_read_ai;
+
+ /* analog output */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 0x0FFF;
+ s->range_table = &range_unipolar10;
+ s->len_chanlist = 16;
+ s->insn_write = dyna_pci10xx_insn_write_ao;
+
+ /* digital input */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->len_chanlist = 16;
+ s->insn_bits = dyna_pci10xx_di_insn_bits;
+
+ /* digital output */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->len_chanlist = 16;
+ s->state = 0;
+ s->insn_bits = dyna_pci10xx_do_insn_bits;
+
+ return 0;
+}
+
+static void dyna_pci10xx_detach(struct comedi_device *dev)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+
+ comedi_pci_detach(dev);
+ if (devpriv)
+ mutex_destroy(&devpriv->mutex);
+}
+
+static struct comedi_driver dyna_pci10xx_driver = {
+ .driver_name = "dyna_pci10xx",
+ .module = THIS_MODULE,
+ .auto_attach = dyna_pci10xx_auto_attach,
+ .detach = dyna_pci10xx_detach,
+};
+
+static int dyna_pci10xx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &dyna_pci10xx_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id dyna_pci10xx_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_PLX, 0x1050) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, dyna_pci10xx_pci_table);
+
+static struct pci_driver dyna_pci10xx_pci_driver = {
+ .name = "dyna_pci10xx",
+ .id_table = dyna_pci10xx_pci_table,
+ .probe = dyna_pci10xx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(dyna_pci10xx_driver, dyna_pci10xx_pci_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Prashant Shah <pshah.mumbai@gmail.com>");
+MODULE_DESCRIPTION("Comedi based drivers for Dynalog PCI DAQ cards");
diff --git a/drivers/staging/comedi/drivers/fl512.c b/drivers/staging/comedi/drivers/fl512.c
new file mode 100644
index 000000000..e1f493241
--- /dev/null
+++ b/drivers/staging/comedi/drivers/fl512.c
@@ -0,0 +1,156 @@
+/*
+ * fl512.c
+ * Anders Gnistrup <ex18@kalman.iau.dtu.dk>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: fl512
+ * Description: unknown
+ * Author: Anders Gnistrup <ex18@kalman.iau.dtu.dk>
+ * Devices: [unknown] FL512 (fl512)
+ * Status: unknown
+ *
+ * Digital I/O is not supported.
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+/*
+ * Register I/O map
+ */
+#define FL512_AI_LSB_REG 0x02
+#define FL512_AI_MSB_REG 0x03
+#define FL512_AI_MUX_REG 0x02
+#define FL512_AI_START_CONV_REG 0x03
+#define FL512_AO_DATA_REG(x) (0x04 + ((x) * 2))
+#define FL512_AO_TRIG_REG(x) (0x04 + ((x) * 2))
+
+static const struct comedi_lrange range_fl512 = {
+ 4, {
+ BIP_RANGE(0.5),
+ BIP_RANGE(1),
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+static int fl512_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ outb(chan, dev->iobase + FL512_AI_MUX_REG);
+
+ for (i = 0; i < insn->n; i++) {
+ outb(0, dev->iobase + FL512_AI_START_CONV_REG);
+
+ /* XXX should test "done" flag instead of delay */
+ udelay(30);
+
+ val = inb(dev->iobase + FL512_AI_LSB_REG);
+ val |= (inb(dev->iobase + FL512_AI_MSB_REG) << 8);
+ val &= s->maxdata;
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int fl512_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* write LSB, MSB then trigger conversion */
+ outb(val & 0x0ff, dev->iobase + FL512_AO_DATA_REG(chan));
+ outb((val >> 8) & 0xf, dev->iobase + FL512_AO_DATA_REG(chan));
+ inb(dev->iobase + FL512_AO_TRIG_REG(chan));
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int fl512_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_fl512;
+ s->insn_read = fl512_ai_insn_read;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_fl512;
+ s->insn_write = fl512_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver fl512_driver = {
+ .driver_name = "fl512",
+ .module = THIS_MODULE,
+ .attach = fl512_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(fl512_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/gsc_hpdi.c b/drivers/staging/comedi/drivers/gsc_hpdi.c
new file mode 100644
index 000000000..3cb6409c4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/gsc_hpdi.c
@@ -0,0 +1,752 @@
+/*
+ * gsc_hpdi.c
+ * Comedi driver the General Standards Corporation
+ * High Speed Parallel Digital Interface rs485 boards.
+ *
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2003 Coherent Imaging Systems
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: gsc_hpdi
+ * Description: General Standards Corporation High
+ * Speed Parallel Digital Interface rs485 boards
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: only receive mode works, transmit not supported
+ * Updated: Thu, 01 Nov 2012 16:17:38 +0000
+ * Devices: [General Standards Corporation] PCI-HPDI32 (gsc_hpdi),
+ * PMC-HPDI32
+ *
+ * Configuration options:
+ * None.
+ *
+ * Manual configuration of supported devices is not supported; they are
+ * configured automatically.
+ *
+ * There are some additional hpdi models available from GSC for which
+ * support could be added to this driver.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "plx9080.h"
+
+/*
+ * PCI BAR2 Register map (dev->mmio)
+ */
+#define FIRMWARE_REV_REG 0x00
+#define FEATURES_REG_PRESENT_BIT (1 << 15)
+#define BOARD_CONTROL_REG 0x04
+#define BOARD_RESET_BIT (1 << 0)
+#define TX_FIFO_RESET_BIT (1 << 1)
+#define RX_FIFO_RESET_BIT (1 << 2)
+#define TX_ENABLE_BIT (1 << 4)
+#define RX_ENABLE_BIT (1 << 5)
+#define DEMAND_DMA_DIRECTION_TX_BIT (1 << 6) /* ch 0 only */
+#define LINE_VALID_ON_STATUS_VALID_BIT (1 << 7)
+#define START_TX_BIT (1 << 8)
+#define CABLE_THROTTLE_ENABLE_BIT (1 << 9)
+#define TEST_MODE_ENABLE_BIT (1 << 31)
+#define BOARD_STATUS_REG 0x08
+#define COMMAND_LINE_STATUS_MASK (0x7f << 0)
+#define TX_IN_PROGRESS_BIT (1 << 7)
+#define TX_NOT_EMPTY_BIT (1 << 8)
+#define TX_NOT_ALMOST_EMPTY_BIT (1 << 9)
+#define TX_NOT_ALMOST_FULL_BIT (1 << 10)
+#define TX_NOT_FULL_BIT (1 << 11)
+#define RX_NOT_EMPTY_BIT (1 << 12)
+#define RX_NOT_ALMOST_EMPTY_BIT (1 << 13)
+#define RX_NOT_ALMOST_FULL_BIT (1 << 14)
+#define RX_NOT_FULL_BIT (1 << 15)
+#define BOARD_JUMPER0_INSTALLED_BIT (1 << 16)
+#define BOARD_JUMPER1_INSTALLED_BIT (1 << 17)
+#define TX_OVERRUN_BIT (1 << 21)
+#define RX_UNDERRUN_BIT (1 << 22)
+#define RX_OVERRUN_BIT (1 << 23)
+#define TX_PROG_ALMOST_REG 0x0c
+#define RX_PROG_ALMOST_REG 0x10
+#define ALMOST_EMPTY_BITS(x) (((x) & 0xffff) << 0)
+#define ALMOST_FULL_BITS(x) (((x) & 0xff) << 16)
+#define FEATURES_REG 0x14
+#define FIFO_SIZE_PRESENT_BIT (1 << 0)
+#define FIFO_WORDS_PRESENT_BIT (1 << 1)
+#define LEVEL_EDGE_INTERRUPTS_PRESENT_BIT (1 << 2)
+#define GPIO_SUPPORTED_BIT (1 << 3)
+#define PLX_DMA_CH1_SUPPORTED_BIT (1 << 4)
+#define OVERRUN_UNDERRUN_SUPPORTED_BIT (1 << 5)
+#define FIFO_REG 0x18
+#define TX_STATUS_COUNT_REG 0x1c
+#define TX_LINE_VALID_COUNT_REG 0x20,
+#define TX_LINE_INVALID_COUNT_REG 0x24
+#define RX_STATUS_COUNT_REG 0x28
+#define RX_LINE_COUNT_REG 0x2c
+#define INTERRUPT_CONTROL_REG 0x30
+#define FRAME_VALID_START_INTR (1 << 0)
+#define FRAME_VALID_END_INTR (1 << 1)
+#define TX_FIFO_EMPTY_INTR (1 << 8)
+#define TX_FIFO_ALMOST_EMPTY_INTR (1 << 9)
+#define TX_FIFO_ALMOST_FULL_INTR (1 << 10)
+#define TX_FIFO_FULL_INTR (1 << 11)
+#define RX_EMPTY_INTR (1 << 12)
+#define RX_ALMOST_EMPTY_INTR (1 << 13)
+#define RX_ALMOST_FULL_INTR (1 << 14)
+#define RX_FULL_INTR (1 << 15)
+#define INTERRUPT_STATUS_REG 0x34
+#define TX_CLOCK_DIVIDER_REG 0x38
+#define TX_FIFO_SIZE_REG 0x40
+#define RX_FIFO_SIZE_REG 0x44
+#define FIFO_SIZE_MASK (0xfffff << 0)
+#define TX_FIFO_WORDS_REG 0x48
+#define RX_FIFO_WORDS_REG 0x4c
+#define INTERRUPT_EDGE_LEVEL_REG 0x50
+#define INTERRUPT_POLARITY_REG 0x54
+
+#define TIMER_BASE 50 /* 20MHz master clock */
+#define DMA_BUFFER_SIZE 0x10000
+#define NUM_DMA_BUFFERS 4
+#define NUM_DMA_DESCRIPTORS 256
+
+struct hpdi_board {
+ const char *name;
+ int device_id;
+ int subdevice_id;
+};
+
+static const struct hpdi_board hpdi_boards[] = {
+ {
+ .name = "pci-hpdi32",
+ .device_id = PCI_DEVICE_ID_PLX_9080,
+ .subdevice_id = 0x2400,
+ },
+#if 0
+ {
+ .name = "pxi-hpdi32",
+ .device_id = 0x9656,
+ .subdevice_id = 0x2705,
+ },
+#endif
+};
+
+struct hpdi_private {
+ void __iomem *plx9080_mmio;
+ uint32_t *dio_buffer[NUM_DMA_BUFFERS]; /* dma buffers */
+ /* physical addresses of dma buffers */
+ dma_addr_t dio_buffer_phys_addr[NUM_DMA_BUFFERS];
+ /* array of dma descriptors read by plx9080, allocated to get proper
+ * alignment */
+ struct plx_dma_desc *dma_desc;
+ /* physical address of dma descriptor array */
+ dma_addr_t dma_desc_phys_addr;
+ unsigned int num_dma_descriptors;
+ /* pointer to start of buffers indexed by descriptor */
+ uint32_t *desc_dio_buffer[NUM_DMA_DESCRIPTORS];
+ /* index of the dma descriptor that is currently being used */
+ unsigned int dma_desc_index;
+ unsigned int tx_fifo_size;
+ unsigned int rx_fifo_size;
+ unsigned long dio_count;
+ /* number of bytes at which to generate COMEDI_CB_BLOCK events */
+ unsigned int block_size;
+};
+
+static void gsc_hpdi_drain_dma(struct comedi_device *dev, unsigned int channel)
+{
+ struct hpdi_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int idx;
+ unsigned int start;
+ unsigned int desc;
+ unsigned int size;
+ unsigned int next;
+
+ if (channel)
+ next = readl(devpriv->plx9080_mmio + PLX_DMA1_PCI_ADDRESS_REG);
+ else
+ next = readl(devpriv->plx9080_mmio + PLX_DMA0_PCI_ADDRESS_REG);
+
+ idx = devpriv->dma_desc_index;
+ start = le32_to_cpu(devpriv->dma_desc[idx].pci_start_addr);
+ /* loop until we have read all the full buffers */
+ for (desc = 0; (next < start || next >= start + devpriv->block_size) &&
+ desc < devpriv->num_dma_descriptors; desc++) {
+ /* transfer data from dma buffer to comedi buffer */
+ size = devpriv->block_size / sizeof(uint32_t);
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (size > devpriv->dio_count)
+ size = devpriv->dio_count;
+ devpriv->dio_count -= size;
+ }
+ comedi_buf_write_samples(s, devpriv->desc_dio_buffer[idx],
+ size);
+ idx++;
+ idx %= devpriv->num_dma_descriptors;
+ start = le32_to_cpu(devpriv->dma_desc[idx].pci_start_addr);
+
+ devpriv->dma_desc_index = idx;
+ }
+ /* XXX check for buffer overrun somehow */
+}
+
+static irqreturn_t gsc_hpdi_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct hpdi_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ uint32_t hpdi_intr_status, hpdi_board_status;
+ uint32_t plx_status;
+ uint32_t plx_bits;
+ uint8_t dma0_status, dma1_status;
+ unsigned long flags;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ plx_status = readl(devpriv->plx9080_mmio + PLX_INTRCS_REG);
+ if ((plx_status & (ICS_DMA0_A | ICS_DMA1_A | ICS_LIA)) == 0)
+ return IRQ_NONE;
+
+ hpdi_intr_status = readl(dev->mmio + INTERRUPT_STATUS_REG);
+ hpdi_board_status = readl(dev->mmio + BOARD_STATUS_REG);
+
+ if (hpdi_intr_status)
+ writel(hpdi_intr_status, dev->mmio + INTERRUPT_STATUS_REG);
+
+ /* spin lock makes sure no one else changes plx dma control reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ dma0_status = readb(devpriv->plx9080_mmio + PLX_DMA0_CS_REG);
+ if (plx_status & ICS_DMA0_A) { /* dma chan 0 interrupt */
+ writeb((dma0_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_mmio + PLX_DMA0_CS_REG);
+
+ if (dma0_status & PLX_DMA_EN_BIT)
+ gsc_hpdi_drain_dma(dev, 0);
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* spin lock makes sure no one else changes plx dma control reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ dma1_status = readb(devpriv->plx9080_mmio + PLX_DMA1_CS_REG);
+ if (plx_status & ICS_DMA1_A) { /* XXX *//* dma chan 1 interrupt */
+ writeb((dma1_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_mmio + PLX_DMA1_CS_REG);
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* clear possible plx9080 interrupt sources */
+ if (plx_status & ICS_LDIA) { /* clear local doorbell interrupt */
+ plx_bits = readl(devpriv->plx9080_mmio + PLX_DBR_OUT_REG);
+ writel(plx_bits, devpriv->plx9080_mmio + PLX_DBR_OUT_REG);
+ }
+
+ if (hpdi_board_status & RX_OVERRUN_BIT) {
+ dev_err(dev->class_dev, "rx fifo overrun\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+
+ if (hpdi_board_status & RX_UNDERRUN_BIT) {
+ dev_err(dev->class_dev, "rx fifo underrun\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+
+ if (devpriv->dio_count == 0)
+ async->events |= COMEDI_CB_EOA;
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static void gsc_hpdi_abort_dma(struct comedi_device *dev, unsigned int channel)
+{
+ struct hpdi_private *devpriv = dev->private;
+ unsigned long flags;
+
+ /* spinlock for plx dma control/status reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ plx9080_abort_dma(devpriv->plx9080_mmio, channel);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static int gsc_hpdi_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ writel(0, dev->mmio + BOARD_CONTROL_REG);
+ writel(0, dev->mmio + INTERRUPT_CONTROL_REG);
+
+ gsc_hpdi_abort_dma(dev, 0);
+
+ return 0;
+}
+
+static int gsc_hpdi_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct hpdi_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long flags;
+ uint32_t bits;
+
+ if (s->io_bits)
+ return -EINVAL;
+
+ writel(RX_FIFO_RESET_BIT, dev->mmio + BOARD_CONTROL_REG);
+
+ gsc_hpdi_abort_dma(dev, 0);
+
+ devpriv->dma_desc_index = 0;
+
+ /*
+ * These register are supposedly unused during chained dma,
+ * but I have found that left over values from last operation
+ * occasionally cause problems with transfer of first dma
+ * block. Initializing them to zero seems to fix the problem.
+ */
+ writel(0, devpriv->plx9080_mmio + PLX_DMA0_TRANSFER_SIZE_REG);
+ writel(0, devpriv->plx9080_mmio + PLX_DMA0_PCI_ADDRESS_REG);
+ writel(0, devpriv->plx9080_mmio + PLX_DMA0_LOCAL_ADDRESS_REG);
+
+ /* give location of first dma descriptor */
+ bits = devpriv->dma_desc_phys_addr | PLX_DESC_IN_PCI_BIT |
+ PLX_INTR_TERM_COUNT | PLX_XFER_LOCAL_TO_PCI;
+ writel(bits, devpriv->plx9080_mmio + PLX_DMA0_DESCRIPTOR_REG);
+
+ /* enable dma transfer */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT | PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_mmio + PLX_DMA0_CS_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->dio_count = cmd->stop_arg;
+ else
+ devpriv->dio_count = 1;
+
+ /* clear over/under run status flags */
+ writel(RX_UNDERRUN_BIT | RX_OVERRUN_BIT, dev->mmio + BOARD_STATUS_REG);
+
+ /* enable interrupts */
+ writel(RX_FULL_INTR, dev->mmio + INTERRUPT_CONTROL_REG);
+
+ writel(RX_ENABLE_BIT, dev->mmio + BOARD_CONTROL_REG);
+
+ return 0;
+}
+
+static int gsc_hpdi_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != i) {
+ dev_dbg(dev->class_dev,
+ "chanlist must be ch 0 to 31 in order\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int gsc_hpdi_cmd_test(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ if (s->io_bits)
+ return -EINVAL;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (!cmd->chanlist_len || !cmd->chanlist) {
+ cmd->chanlist_len = 32;
+ err |= -EINVAL;
+ }
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= gsc_hpdi_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+/* setup dma descriptors so a link completes every 'len' bytes */
+static int gsc_hpdi_setup_dma_descriptors(struct comedi_device *dev,
+ unsigned int len)
+{
+ struct hpdi_private *devpriv = dev->private;
+ dma_addr_t phys_addr = devpriv->dma_desc_phys_addr;
+ uint32_t next_bits = PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT |
+ PLX_XFER_LOCAL_TO_PCI;
+ unsigned int offset = 0;
+ unsigned int idx = 0;
+ unsigned int i;
+
+ if (len > DMA_BUFFER_SIZE)
+ len = DMA_BUFFER_SIZE;
+ len -= len % sizeof(uint32_t);
+ if (len == 0)
+ return -EINVAL;
+
+ for (i = 0; i < NUM_DMA_DESCRIPTORS && idx < NUM_DMA_BUFFERS; i++) {
+ devpriv->dma_desc[i].pci_start_addr =
+ cpu_to_le32(devpriv->dio_buffer_phys_addr[idx] + offset);
+ devpriv->dma_desc[i].local_start_addr = cpu_to_le32(FIFO_REG);
+ devpriv->dma_desc[i].transfer_size = cpu_to_le32(len);
+ devpriv->dma_desc[i].next = cpu_to_le32((phys_addr +
+ (i + 1) * sizeof(devpriv->dma_desc[0])) | next_bits);
+
+ devpriv->desc_dio_buffer[i] = devpriv->dio_buffer[idx] +
+ (offset / sizeof(uint32_t));
+
+ offset += len;
+ if (len + offset > DMA_BUFFER_SIZE) {
+ offset = 0;
+ idx++;
+ }
+ }
+ devpriv->num_dma_descriptors = i;
+ /* fix last descriptor to point back to first */
+ devpriv->dma_desc[i - 1].next = cpu_to_le32(phys_addr | next_bits);
+
+ devpriv->block_size = len;
+
+ return len;
+}
+
+static int gsc_hpdi_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ switch (data[0]) {
+ case INSN_CONFIG_BLOCK_SIZE:
+ ret = gsc_hpdi_setup_dma_descriptors(dev, data[1]);
+ if (ret)
+ return ret;
+
+ data[1] = ret;
+ break;
+ default:
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0xffffffff);
+ if (ret)
+ return ret;
+ break;
+ }
+
+ return insn->n;
+}
+
+static void gsc_hpdi_free_dma(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct hpdi_private *devpriv = dev->private;
+ int i;
+
+ if (!devpriv)
+ return;
+
+ /* free pci dma buffers */
+ for (i = 0; i < NUM_DMA_BUFFERS; i++) {
+ if (devpriv->dio_buffer[i])
+ pci_free_consistent(pcidev,
+ DMA_BUFFER_SIZE,
+ devpriv->dio_buffer[i],
+ devpriv->dio_buffer_phys_addr[i]);
+ }
+ /* free dma descriptors */
+ if (devpriv->dma_desc)
+ pci_free_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ NUM_DMA_DESCRIPTORS,
+ devpriv->dma_desc,
+ devpriv->dma_desc_phys_addr);
+}
+
+static int gsc_hpdi_init(struct comedi_device *dev)
+{
+ struct hpdi_private *devpriv = dev->private;
+ uint32_t plx_intcsr_bits;
+
+ /* wait 10usec after reset before accessing fifos */
+ writel(BOARD_RESET_BIT, dev->mmio + BOARD_CONTROL_REG);
+ udelay(10);
+
+ writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32),
+ dev->mmio + RX_PROG_ALMOST_REG);
+ writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32),
+ dev->mmio + TX_PROG_ALMOST_REG);
+
+ devpriv->tx_fifo_size = readl(dev->mmio + TX_FIFO_SIZE_REG) &
+ FIFO_SIZE_MASK;
+ devpriv->rx_fifo_size = readl(dev->mmio + RX_FIFO_SIZE_REG) &
+ FIFO_SIZE_MASK;
+
+ writel(0, dev->mmio + INTERRUPT_CONTROL_REG);
+
+ /* enable interrupts */
+ plx_intcsr_bits =
+ ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE |
+ ICS_DMA0_E;
+ writel(plx_intcsr_bits, devpriv->plx9080_mmio + PLX_INTRCS_REG);
+
+ return 0;
+}
+
+static void gsc_hpdi_init_plx9080(struct comedi_device *dev)
+{
+ struct hpdi_private *devpriv = dev->private;
+ uint32_t bits;
+ void __iomem *plx_iobase = devpriv->plx9080_mmio;
+
+#ifdef __BIG_ENDIAN
+ bits = BIGEND_DMA0 | BIGEND_DMA1;
+#else
+ bits = 0;
+#endif
+ writel(bits, devpriv->plx9080_mmio + PLX_BIGEND_REG);
+
+ writel(0, devpriv->plx9080_mmio + PLX_INTRCS_REG);
+
+ gsc_hpdi_abort_dma(dev, 0);
+ gsc_hpdi_abort_dma(dev, 1);
+
+ /* configure dma0 mode */
+ bits = 0;
+ /* enable ready input */
+ bits |= PLX_DMA_EN_READYIN_BIT;
+ /* enable dma chaining */
+ bits |= PLX_EN_CHAIN_BIT;
+ /* enable interrupt on dma done
+ * (probably don't need this, since chain never finishes) */
+ bits |= PLX_EN_DMA_DONE_INTR_BIT;
+ /* don't increment local address during transfers
+ * (we are transferring from a fixed fifo register) */
+ bits |= PLX_LOCAL_ADDR_CONST_BIT;
+ /* route dma interrupt to pci bus */
+ bits |= PLX_DMA_INTR_PCI_BIT;
+ /* enable demand mode */
+ bits |= PLX_DEMAND_MODE_BIT;
+ /* enable local burst mode */
+ bits |= PLX_DMA_LOCAL_BURST_EN_BIT;
+ bits |= PLX_LOCAL_BUS_32_WIDE_BITS;
+ writel(bits, plx_iobase + PLX_DMA0_MODE_REG);
+}
+
+static const struct hpdi_board *gsc_hpdi_find_board(struct pci_dev *pcidev)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(hpdi_boards); i++)
+ if (pcidev->device == hpdi_boards[i].device_id &&
+ pcidev->subsystem_device == hpdi_boards[i].subdevice_id)
+ return &hpdi_boards[i];
+ return NULL;
+}
+
+static int gsc_hpdi_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct hpdi_board *thisboard;
+ struct hpdi_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ int retval;
+
+ thisboard = gsc_hpdi_find_board(pcidev);
+ if (!thisboard) {
+ dev_err(dev->class_dev, "gsc_hpdi: pci %s not supported\n",
+ pci_name(pcidev));
+ return -EINVAL;
+ }
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ retval = comedi_pci_enable(dev);
+ if (retval)
+ return retval;
+ pci_set_master(pcidev);
+
+ devpriv->plx9080_mmio = pci_ioremap_bar(pcidev, 0);
+ dev->mmio = pci_ioremap_bar(pcidev, 2);
+ if (!devpriv->plx9080_mmio || !dev->mmio) {
+ dev_warn(dev->class_dev, "failed to remap io memory\n");
+ return -ENOMEM;
+ }
+
+ gsc_hpdi_init_plx9080(dev);
+
+ /* get irq */
+ if (request_irq(pcidev->irq, gsc_hpdi_interrupt, IRQF_SHARED,
+ dev->board_name, dev)) {
+ dev_warn(dev->class_dev,
+ "unable to allocate irq %u\n", pcidev->irq);
+ return -EINVAL;
+ }
+ dev->irq = pcidev->irq;
+
+ dev_dbg(dev->class_dev, " irq %u\n", dev->irq);
+
+ /* allocate pci dma buffers */
+ for (i = 0; i < NUM_DMA_BUFFERS; i++) {
+ devpriv->dio_buffer[i] =
+ pci_alloc_consistent(pcidev, DMA_BUFFER_SIZE,
+ &devpriv->dio_buffer_phys_addr[i]);
+ }
+ /* allocate dma descriptors */
+ devpriv->dma_desc = pci_alloc_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ NUM_DMA_DESCRIPTORS,
+ &devpriv->dma_desc_phys_addr);
+ if (devpriv->dma_desc_phys_addr & 0xf) {
+ dev_warn(dev->class_dev,
+ " dma descriptors not quad-word aligned (bug)\n");
+ return -EIO;
+ }
+
+ retval = gsc_hpdi_setup_dma_descriptors(dev, 0x1000);
+ if (retval < 0)
+ return retval;
+
+ retval = comedi_alloc_subdevices(dev, 1);
+ if (retval)
+ return retval;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL |
+ SDF_CMD_READ;
+ s->n_chan = 32;
+ s->len_chanlist = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = gsc_hpdi_dio_insn_config;
+ s->do_cmd = gsc_hpdi_cmd;
+ s->do_cmdtest = gsc_hpdi_cmd_test;
+ s->cancel = gsc_hpdi_cancel;
+
+ return gsc_hpdi_init(dev);
+}
+
+static void gsc_hpdi_detach(struct comedi_device *dev)
+{
+ struct hpdi_private *devpriv = dev->private;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->plx9080_mmio) {
+ writel(0, devpriv->plx9080_mmio + PLX_INTRCS_REG);
+ iounmap(devpriv->plx9080_mmio);
+ }
+ if (dev->mmio)
+ iounmap(dev->mmio);
+ }
+ comedi_pci_disable(dev);
+ gsc_hpdi_free_dma(dev);
+}
+
+static struct comedi_driver gsc_hpdi_driver = {
+ .driver_name = "gsc_hpdi",
+ .module = THIS_MODULE,
+ .auto_attach = gsc_hpdi_auto_attach,
+ .detach = gsc_hpdi_detach,
+};
+
+static int gsc_hpdi_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &gsc_hpdi_driver, id->driver_data);
+}
+
+static const struct pci_device_id gsc_hpdi_pci_table[] = {
+ { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9080, PCI_VENDOR_ID_PLX,
+ 0x2400, 0, 0, 0},
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, gsc_hpdi_pci_table);
+
+static struct pci_driver gsc_hpdi_pci_driver = {
+ .name = "gsc_hpdi",
+ .id_table = gsc_hpdi_pci_table,
+ .probe = gsc_hpdi_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(gsc_hpdi_driver, gsc_hpdi_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/icp_multi.c b/drivers/staging/comedi/drivers/icp_multi.c
new file mode 100644
index 000000000..1e104ebf8
--- /dev/null
+++ b/drivers/staging/comedi/drivers/icp_multi.c
@@ -0,0 +1,568 @@
+/*
+ comedi/drivers/icp_multi.c
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+Driver: icp_multi
+Description: Inova ICP_MULTI
+Author: Anne Smorthit <anne.smorthit@sfwte.ch>
+Devices: [Inova] ICP_MULTI (icp_multi)
+Status: works
+
+The driver works for analog input and output and digital input and output.
+It does not work with interrupts or with the counters. Currently no support
+for DMA.
+
+It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
+resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA. Input
+ranges can be individually programmed for each channel. Voltage or current
+measurement is selected by jumper.
+
+There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V
+
+16 x Digital Inputs, 24V
+
+8 x Digital Outputs, 24V, 1A
+
+4 x 16-bit counters
+
+Configuration options: not applicable, uses PCI auto config
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#define ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */
+#define ICP_MULTI_AI 2 /* R: Analogue input data */
+#define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */
+#define ICP_MULTI_AO 6 /* R/W: Analogue output data */
+#define ICP_MULTI_DI 8 /* R/W: Digital inputs */
+#define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */
+#define ICP_MULTI_INT_EN 0x0C /* R/W: Interrupt enable register */
+#define ICP_MULTI_INT_STAT 0x0E /* R/W: Interrupt status register */
+#define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */
+#define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */
+#define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */
+#define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */
+
+/* Define bits from ADC command/status register */
+#define ADC_ST 0x0001 /* Start ADC */
+#define ADC_BSY 0x0001 /* ADC busy */
+#define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */
+#define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
+#define ADC_DI 0x0040 /* Differential input mode 1 = differential */
+
+/* Define bits from DAC command/status register */
+#define DAC_ST 0x0001 /* Start DAC */
+#define DAC_BSY 0x0001 /* DAC busy */
+#define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */
+#define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
+
+/* Define bits from interrupt enable/status registers */
+#define ADC_READY 0x0001 /* A/d conversion ready interrupt */
+#define DAC_READY 0x0002 /* D/a conversion ready interrupt */
+#define DOUT_ERROR 0x0004 /* Digital output error interrupt */
+#define DIN_STATUS 0x0008 /* Digital input status change interrupt */
+#define CIE0 0x0010 /* Counter 0 overrun interrupt */
+#define CIE1 0x0020 /* Counter 1 overrun interrupt */
+#define CIE2 0x0040 /* Counter 2 overrun interrupt */
+#define CIE3 0x0080 /* Counter 3 overrun interrupt */
+
+/* Useful definitions */
+#define Status_IRQ 0x00ff /* All interrupts */
+
+/* Define analogue range */
+static const struct comedi_lrange range_analog = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10)
+ }
+};
+
+static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
+
+/*
+==============================================================================
+ Data & Structure declarations
+==============================================================================
+*/
+
+struct icp_multi_private {
+ unsigned int AdcCmdStatus; /* ADC Command/Status register */
+ unsigned int DacCmdStatus; /* DAC Command/Status register */
+ unsigned int IntEnable; /* Interrupt Enable register */
+ unsigned int IntStatus; /* Interrupt Status register */
+ unsigned int act_chanlist[32]; /* list of scanned channel */
+ unsigned char act_chanlist_len; /* len of scanlist */
+ unsigned char act_chanlist_pos; /* actual position in MUX list */
+ unsigned int *ai_chanlist; /* actaul chanlist */
+ unsigned int do_data; /* Remember digital output data */
+};
+
+static void setup_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist, unsigned int n_chan)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ unsigned int i, range, chanprog;
+ unsigned int diff;
+
+ devpriv->act_chanlist_len = n_chan;
+ devpriv->act_chanlist_pos = 0;
+
+ for (i = 0; i < n_chan; i++) {
+ /* Get channel */
+ chanprog = CR_CHAN(chanlist[i]);
+
+ /* Determine if it is a differential channel (Bit 15 = 1) */
+ if (CR_AREF(chanlist[i]) == AREF_DIFF) {
+ diff = 1;
+ chanprog &= 0x0007;
+ } else {
+ diff = 0;
+ chanprog &= 0x000f;
+ }
+
+ /* Clear channel, range and input mode bits
+ * in A/D command/status register */
+ devpriv->AdcCmdStatus &= 0xf00f;
+
+ /* Set channel number and differential mode status bit */
+ if (diff) {
+ /* Set channel number, bits 9-11 & mode, bit 6 */
+ devpriv->AdcCmdStatus |= (chanprog << 9);
+ devpriv->AdcCmdStatus |= ADC_DI;
+ } else
+ /* Set channel number, bits 8-11 */
+ devpriv->AdcCmdStatus |= (chanprog << 8);
+
+ /* Get range for current channel */
+ range = range_codes_analog[CR_RANGE(chanlist[i])];
+ /* Set range. bits 4-5 */
+ devpriv->AdcCmdStatus |= range;
+
+ /* Output channel, range, mode to ICP Multi */
+ writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
+ }
+}
+
+static int icp_multi_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readw(dev->mmio + ICP_MULTI_ADC_CSR);
+ if ((status & ADC_BSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int icp_multi_insn_read_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ int ret = 0;
+ int n;
+
+ /* Disable A/D conversion ready interrupt */
+ devpriv->IntEnable &= ~ADC_READY;
+ writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
+
+ /* Clear interrupt status */
+ devpriv->IntStatus |= ADC_READY;
+ writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
+
+ /* Set up appropriate channel, mode and range data, for specified ch */
+ setup_channel_list(dev, s, &insn->chanspec, 1);
+
+ for (n = 0; n < insn->n; n++) {
+ /* Set start ADC bit */
+ devpriv->AdcCmdStatus |= ADC_ST;
+ writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
+ devpriv->AdcCmdStatus &= ~ADC_ST;
+
+ udelay(1);
+
+ /* Wait for conversion to complete, or get fed up waiting */
+ ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff;
+ }
+
+ /* Disable interrupt */
+ devpriv->IntEnable &= ~ADC_READY;
+ writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
+
+ /* Clear interrupt status */
+ devpriv->IntStatus |= ADC_READY;
+ writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
+
+ return ret ? ret : n;
+}
+
+static int icp_multi_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readw(dev->mmio + ICP_MULTI_DAC_CSR);
+ if ((status & DAC_BSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int icp_multi_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int i;
+
+ /* Disable D/A conversion ready interrupt */
+ devpriv->IntEnable &= ~DAC_READY;
+ writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
+
+ /* Clear interrupt status */
+ devpriv->IntStatus |= DAC_READY;
+ writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
+
+ /* Set up range and channel data */
+ /* Bit 4 = 1 : Bipolar */
+ /* Bit 5 = 0 : 5V */
+ /* Bit 5 = 1 : 10V */
+ /* Bits 8-9 : Channel number */
+ devpriv->DacCmdStatus &= 0xfccf;
+ devpriv->DacCmdStatus |= range_codes_analog[range];
+ devpriv->DacCmdStatus |= (chan << 8);
+
+ writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+ int ret;
+
+ /* Wait for analogue output data register to be
+ * ready for new data, or get fed up waiting */
+ ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0);
+ if (ret) {
+ /* Disable interrupt */
+ devpriv->IntEnable &= ~DAC_READY;
+ writew(devpriv->IntEnable,
+ dev->mmio + ICP_MULTI_INT_EN);
+
+ /* Clear interrupt status */
+ devpriv->IntStatus |= DAC_READY;
+ writew(devpriv->IntStatus,
+ dev->mmio + ICP_MULTI_INT_STAT);
+
+ return ret;
+ }
+
+ writew(val, dev->mmio + ICP_MULTI_AO);
+
+ /* Set DAC_ST bit to write the data to selected channel */
+ devpriv->DacCmdStatus |= DAC_ST;
+ writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
+ devpriv->DacCmdStatus &= ~DAC_ST;
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int icp_multi_insn_bits_di(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = readw(dev->mmio + ICP_MULTI_DI);
+
+ return insn->n;
+}
+
+static int icp_multi_insn_bits_do(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ writew(s->state, dev->mmio + ICP_MULTI_DO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int icp_multi_insn_read_ctr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ return 0;
+}
+
+static int icp_multi_insn_write_ctr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ return 0;
+}
+
+static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ int int_no;
+
+ /* Is this interrupt from our board? */
+ int_no = readw(dev->mmio + ICP_MULTI_INT_STAT) & Status_IRQ;
+ if (!int_no)
+ /* No, exit */
+ return IRQ_NONE;
+
+ /* Determine which interrupt is active & handle it */
+ switch (int_no) {
+ case ADC_READY:
+ break;
+ case DAC_READY:
+ break;
+ case DOUT_ERROR:
+ break;
+ case DIN_STATUS:
+ break;
+ case CIE0:
+ break;
+ case CIE1:
+ break;
+ case CIE2:
+ break;
+ case CIE3:
+ break;
+ default:
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+#if 0
+static int check_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist, unsigned int n_chan)
+{
+ unsigned int i;
+
+ /* Check that we at least have one channel to check */
+ if (n_chan < 1) {
+ dev_err(dev->class_dev, "range/channel list is empty!\n");
+ return 0;
+ }
+ /* Check all channels */
+ for (i = 0; i < n_chan; i++) {
+ /* Check that channel number is < maximum */
+ if (CR_AREF(chanlist[i]) == AREF_DIFF) {
+ if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
+ dev_err(dev->class_dev,
+ "Incorrect differential ai ch-nr\n");
+ return 0;
+ }
+ } else {
+ if (CR_CHAN(chanlist[i]) > s->n_chan) {
+ dev_err(dev->class_dev,
+ "Incorrect ai channel number\n");
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+#endif
+
+static int icp_multi_reset(struct comedi_device *dev)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ unsigned int i;
+
+ /* Clear INT enables and requests */
+ writew(0, dev->mmio + ICP_MULTI_INT_EN);
+ writew(0x00ff, dev->mmio + ICP_MULTI_INT_STAT);
+
+ /* Set DACs to 0..5V range and 0V output */
+ for (i = 0; i < 4; i++) {
+ devpriv->DacCmdStatus &= 0xfcce;
+
+ /* Set channel number */
+ devpriv->DacCmdStatus |= (i << 8);
+
+ /* Output 0V */
+ writew(0, dev->mmio + ICP_MULTI_AO);
+
+ /* Set start conversion bit */
+ devpriv->DacCmdStatus |= DAC_ST;
+
+ /* Output to command / status register */
+ writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
+
+ /* Delay to allow DAC time to recover */
+ udelay(1);
+ }
+
+ /* Digital outputs to 0 */
+ writew(0, dev->mmio + ICP_MULTI_DO);
+
+ return 0;
+}
+
+static int icp_multi_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct icp_multi_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 2);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ icp_multi_reset(dev);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 16;
+ s->range_table = &range_analog;
+ s->insn_read = icp_multi_insn_read_ai;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 4;
+ s->range_table = &range_analog;
+ s->insn_write = icp_multi_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->len_chanlist = 16;
+ s->range_table = &range_digital;
+ s->insn_bits = icp_multi_insn_bits_di;
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->len_chanlist = 8;
+ s->range_table = &range_digital;
+ s->insn_bits = icp_multi_insn_bits_do;
+
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 4;
+ s->state = 0;
+ s->insn_read = icp_multi_insn_read_ctr;
+ s->insn_write = icp_multi_insn_write_ctr;
+
+ return 0;
+}
+
+static void icp_multi_detach(struct comedi_device *dev)
+{
+ if (dev->mmio)
+ icp_multi_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver icp_multi_driver = {
+ .driver_name = "icp_multi",
+ .module = THIS_MODULE,
+ .auto_attach = icp_multi_auto_attach,
+ .detach = icp_multi_detach,
+};
+
+static int icp_multi_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
+}
+
+static const struct pci_device_id icp_multi_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
+
+static struct pci_driver icp_multi_pci_driver = {
+ .name = "icp_multi",
+ .id_table = icp_multi_pci_table,
+ .probe = icp_multi_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ii_pci20kc.c b/drivers/staging/comedi/drivers/ii_pci20kc.c
new file mode 100644
index 000000000..0768bc42a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ii_pci20kc.c
@@ -0,0 +1,526 @@
+/*
+ * ii_pci20kc.c
+ * Driver for Intelligent Instruments PCI-20001C carrier board and modules.
+ *
+ * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de>
+ * with suggestions from David Schleef 16.06.2000
+ */
+
+/*
+ * Driver: ii_pci20kc
+ * Description: Intelligent Instruments PCI-20001C carrier board
+ * Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc)
+ * Author: Markus Kempf <kempf@matsci.uni-sb.de>
+ * Status: works
+ *
+ * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The
+ * -2a version has 32 on-board DIO channels. Three add-on modules
+ * can be added to the carrier board for additional functionality.
+ *
+ * Supported add-on modules:
+ * PCI-20006M-1 1 channel, 16-bit analog output module
+ * PCI-20006M-2 2 channel, 16-bit analog output module
+ * PCI-20341M-1A 4 channel, 16-bit analog input module
+ *
+ * Options:
+ * 0 Board base address
+ * 1 IRQ (not-used)
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define II20K_SIZE 0x400
+#define II20K_MOD_OFFSET 0x100
+#define II20K_ID_REG 0x00
+#define II20K_ID_MOD1_EMPTY (1 << 7)
+#define II20K_ID_MOD2_EMPTY (1 << 6)
+#define II20K_ID_MOD3_EMPTY (1 << 5)
+#define II20K_ID_MASK 0x1f
+#define II20K_ID_PCI20001C_1A 0x1b /* no on-board DIO */
+#define II20K_ID_PCI20001C_2A 0x1d /* on-board DIO */
+#define II20K_MOD_STATUS_REG 0x40
+#define II20K_MOD_STATUS_IRQ_MOD1 (1 << 7)
+#define II20K_MOD_STATUS_IRQ_MOD2 (1 << 6)
+#define II20K_MOD_STATUS_IRQ_MOD3 (1 << 5)
+#define II20K_DIO0_REG 0x80
+#define II20K_DIO1_REG 0x81
+#define II20K_DIR_ENA_REG 0x82
+#define II20K_DIR_DIO3_OUT (1 << 7)
+#define II20K_DIR_DIO2_OUT (1 << 6)
+#define II20K_BUF_DISAB_DIO3 (1 << 5)
+#define II20K_BUF_DISAB_DIO2 (1 << 4)
+#define II20K_DIR_DIO1_OUT (1 << 3)
+#define II20K_DIR_DIO0_OUT (1 << 2)
+#define II20K_BUF_DISAB_DIO1 (1 << 1)
+#define II20K_BUF_DISAB_DIO0 (1 << 0)
+#define II20K_CTRL01_REG 0x83
+#define II20K_CTRL01_SET (1 << 7)
+#define II20K_CTRL01_DIO0_IN (1 << 4)
+#define II20K_CTRL01_DIO1_IN (1 << 1)
+#define II20K_DIO2_REG 0xc0
+#define II20K_DIO3_REG 0xc1
+#define II20K_CTRL23_REG 0xc3
+#define II20K_CTRL23_SET (1 << 7)
+#define II20K_CTRL23_DIO2_IN (1 << 4)
+#define II20K_CTRL23_DIO3_IN (1 << 1)
+
+#define II20K_ID_PCI20006M_1 0xe2 /* 1 AO channels */
+#define II20K_ID_PCI20006M_2 0xe3 /* 2 AO channels */
+#define II20K_AO_STRB_REG(x) (0x0b + ((x) * 0x08))
+#define II20K_AO_LSB_REG(x) (0x0d + ((x) * 0x08))
+#define II20K_AO_MSB_REG(x) (0x0e + ((x) * 0x08))
+#define II20K_AO_STRB_BOTH_REG 0x1b
+
+#define II20K_ID_PCI20341M_1 0x77 /* 4 AI channels */
+#define II20K_AI_STATUS_CMD_REG 0x01
+#define II20K_AI_STATUS_CMD_BUSY (1 << 7)
+#define II20K_AI_STATUS_CMD_HW_ENA (1 << 1)
+#define II20K_AI_STATUS_CMD_EXT_START (1 << 0)
+#define II20K_AI_LSB_REG 0x02
+#define II20K_AI_MSB_REG 0x03
+#define II20K_AI_PACER_RESET_REG 0x04
+#define II20K_AI_16BIT_DATA_REG 0x06
+#define II20K_AI_CONF_REG 0x10
+#define II20K_AI_CONF_ENA (1 << 2)
+#define II20K_AI_OPT_REG 0x11
+#define II20K_AI_OPT_TRIG_ENA (1 << 5)
+#define II20K_AI_OPT_TRIG_INV (1 << 4)
+#define II20K_AI_OPT_TIMEBASE(x) (((x) & 0x3) << 1)
+#define II20K_AI_OPT_BURST_MODE (1 << 0)
+#define II20K_AI_STATUS_REG 0x12
+#define II20K_AI_STATUS_INT (1 << 7)
+#define II20K_AI_STATUS_TRIG (1 << 6)
+#define II20K_AI_STATUS_TRIG_ENA (1 << 5)
+#define II20K_AI_STATUS_PACER_ERR (1 << 2)
+#define II20K_AI_STATUS_DATA_ERR (1 << 1)
+#define II20K_AI_STATUS_SET_TIME_ERR (1 << 0)
+#define II20K_AI_LAST_CHAN_ADDR_REG 0x13
+#define II20K_AI_CUR_ADDR_REG 0x14
+#define II20K_AI_SET_TIME_REG 0x15
+#define II20K_AI_DELAY_LSB_REG 0x16
+#define II20K_AI_DELAY_MSB_REG 0x17
+#define II20K_AI_CHAN_ADV_REG 0x18
+#define II20K_AI_CHAN_RESET_REG 0x19
+#define II20K_AI_START_TRIG_REG 0x1a
+#define II20K_AI_COUNT_RESET_REG 0x1b
+#define II20K_AI_CHANLIST_REG 0x80
+#define II20K_AI_CHANLIST_ONBOARD_ONLY (1 << 5)
+#define II20K_AI_CHANLIST_GAIN(x) (((x) & 0x3) << 3)
+#define II20K_AI_CHANLIST_MUX_ENA (1 << 2)
+#define II20K_AI_CHANLIST_CHAN(x) (((x) & 0x3) << 0)
+#define II20K_AI_CHANLIST_LEN 0x80
+
+/* the AO range is set by jumpers on the 20006M module */
+static const struct comedi_lrange ii20k_ao_ranges = {
+ 3, {
+ BIP_RANGE(5), /* Chan 0 - W1/W3 in Chan 1 - W2/W4 in */
+ UNI_RANGE(10), /* Chan 0 - W1/W3 out Chan 1 - W2/W4 in */
+ BIP_RANGE(10) /* Chan 0 - W1/W3 in Chan 1 - W2/W4 out */
+ }
+};
+
+static const struct comedi_lrange ii20k_ai_ranges = {
+ 4, {
+ BIP_RANGE(5), /* gain 1 */
+ BIP_RANGE(0.5), /* gain 10 */
+ BIP_RANGE(0.05), /* gain 100 */
+ BIP_RANGE(0.025) /* gain 200 */
+ },
+};
+
+static void __iomem *ii20k_module_iobase(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET;
+}
+
+static int ii20k_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ /* munge data */
+ val += ((s->maxdata + 1) >> 1);
+ val &= s->maxdata;
+
+ writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan));
+ writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan));
+ writeb(0x00, iobase + II20K_AO_STRB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int ii20k_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned char status;
+
+ status = readb(iobase + II20K_AI_STATUS_REG);
+ if ((status & II20K_AI_STATUS_INT) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static void ii20k_ai_setup(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned char val;
+
+ /* initialize module */
+ writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG);
+
+ /* software conversion */
+ writeb(0, iobase + II20K_AI_STATUS_CMD_REG);
+
+ /* set the time base for the settling time counter based on the gain */
+ val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2);
+ writeb(val, iobase + II20K_AI_OPT_REG);
+
+ /* set the settling time counter based on the gain */
+ val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99;
+ writeb(val, iobase + II20K_AI_SET_TIME_REG);
+
+ /* set number of input channels */
+ writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG);
+
+ /* set the channel list byte */
+ val = II20K_AI_CHANLIST_ONBOARD_ONLY |
+ II20K_AI_CHANLIST_MUX_ENA |
+ II20K_AI_CHANLIST_GAIN(range) |
+ II20K_AI_CHANLIST_CHAN(chan);
+ writeb(val, iobase + II20K_AI_CHANLIST_REG);
+
+ /* reset settling time counter and trigger delay counter */
+ writeb(0, iobase + II20K_AI_COUNT_RESET_REG);
+
+ /* reset channel scanner */
+ writeb(0, iobase + II20K_AI_CHAN_RESET_REG);
+}
+
+static int ii20k_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ int ret;
+ int i;
+
+ ii20k_ai_setup(dev, s, insn->chanspec);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val;
+
+ /* generate a software start convert signal */
+ readb(iobase + II20K_AI_PACER_RESET_REG);
+
+ ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = readb(iobase + II20K_AI_LSB_REG);
+ val |= (readb(iobase + II20K_AI_MSB_REG) << 8);
+
+ /* munge two's complement data */
+ val += ((s->maxdata + 1) >> 1);
+ val &= s->maxdata;
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static void ii20k_dio_config(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned char ctrl01 = 0;
+ unsigned char ctrl23 = 0;
+ unsigned char dir_ena = 0;
+
+ /* port 0 - channels 0-7 */
+ if (s->io_bits & 0x000000ff) {
+ /* output port */
+ ctrl01 &= ~II20K_CTRL01_DIO0_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO0;
+ dir_ena |= II20K_DIR_DIO0_OUT;
+ } else {
+ /* input port */
+ ctrl01 |= II20K_CTRL01_DIO0_IN;
+ dir_ena &= ~II20K_DIR_DIO0_OUT;
+ }
+
+ /* port 1 - channels 8-15 */
+ if (s->io_bits & 0x0000ff00) {
+ /* output port */
+ ctrl01 &= ~II20K_CTRL01_DIO1_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO1;
+ dir_ena |= II20K_DIR_DIO1_OUT;
+ } else {
+ /* input port */
+ ctrl01 |= II20K_CTRL01_DIO1_IN;
+ dir_ena &= ~II20K_DIR_DIO1_OUT;
+ }
+
+ /* port 2 - channels 16-23 */
+ if (s->io_bits & 0x00ff0000) {
+ /* output port */
+ ctrl23 &= ~II20K_CTRL23_DIO2_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO2;
+ dir_ena |= II20K_DIR_DIO2_OUT;
+ } else {
+ /* input port */
+ ctrl23 |= II20K_CTRL23_DIO2_IN;
+ dir_ena &= ~II20K_DIR_DIO2_OUT;
+ }
+
+ /* port 3 - channels 24-31 */
+ if (s->io_bits & 0xff000000) {
+ /* output port */
+ ctrl23 &= ~II20K_CTRL23_DIO3_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO3;
+ dir_ena |= II20K_DIR_DIO3_OUT;
+ } else {
+ /* input port */
+ ctrl23 |= II20K_CTRL23_DIO3_IN;
+ dir_ena &= ~II20K_DIR_DIO3_OUT;
+ }
+
+ ctrl23 |= II20K_CTRL01_SET;
+ ctrl23 |= II20K_CTRL23_SET;
+
+ /* order is important */
+ writeb(ctrl01, dev->mmio + II20K_CTRL01_REG);
+ writeb(ctrl23, dev->mmio + II20K_CTRL23_REG);
+ writeb(dir_ena, dev->mmio + II20K_DIR_ENA_REG);
+}
+
+static int ii20k_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ ii20k_dio_config(dev, s);
+
+ return insn->n;
+}
+
+static int ii20k_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x000000ff)
+ writeb((s->state >> 0) & 0xff,
+ dev->mmio + II20K_DIO0_REG);
+ if (mask & 0x0000ff00)
+ writeb((s->state >> 8) & 0xff,
+ dev->mmio + II20K_DIO1_REG);
+ if (mask & 0x00ff0000)
+ writeb((s->state >> 16) & 0xff,
+ dev->mmio + II20K_DIO2_REG);
+ if (mask & 0xff000000)
+ writeb((s->state >> 24) & 0xff,
+ dev->mmio + II20K_DIO3_REG);
+ }
+
+ data[1] = readb(dev->mmio + II20K_DIO0_REG);
+ data[1] |= readb(dev->mmio + II20K_DIO1_REG) << 8;
+ data[1] |= readb(dev->mmio + II20K_DIO2_REG) << 16;
+ data[1] |= readb(dev->mmio + II20K_DIO3_REG) << 24;
+
+ return insn->n;
+}
+
+static int ii20k_init_module(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned char id;
+ int ret;
+
+ id = readb(iobase + II20K_ID_REG);
+ switch (id) {
+ case II20K_ID_PCI20006M_1:
+ case II20K_ID_PCI20006M_2:
+ /* Analog Output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = (id == II20K_ID_PCI20006M_2) ? 2 : 1;
+ s->maxdata = 0xffff;
+ s->range_table = &ii20k_ao_ranges;
+ s->insn_write = ii20k_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ break;
+ case II20K_ID_PCI20341M_1:
+ /* Analog Input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->range_table = &ii20k_ai_ranges;
+ s->insn_read = ii20k_ai_insn_read;
+ break;
+ default:
+ s->type = COMEDI_SUBD_UNUSED;
+ break;
+ }
+
+ return 0;
+}
+
+static int ii20k_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ unsigned int membase;
+ unsigned char id;
+ bool has_dio;
+ int ret;
+
+ membase = it->options[0];
+ if (!membase || (membase & ~(0x100000 - II20K_SIZE))) {
+ dev_warn(dev->class_dev,
+ "%s: invalid memory address specified\n",
+ dev->board_name);
+ return -EINVAL;
+ }
+
+ if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) {
+ dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n",
+ dev->board_name, membase, II20K_SIZE);
+ return -EIO;
+ }
+ dev->iobase = membase; /* actually, a memory address */
+
+ dev->mmio = ioremap(membase, II20K_SIZE);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ id = readb(dev->mmio + II20K_ID_REG);
+ switch (id & II20K_ID_MASK) {
+ case II20K_ID_PCI20001C_1A:
+ has_dio = false;
+ break;
+ case II20K_ID_PCI20001C_2A:
+ has_dio = true;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ if (id & II20K_ID_MOD1_EMPTY) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = ii20k_init_module(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ s = &dev->subdevices[1];
+ if (id & II20K_ID_MOD2_EMPTY) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = ii20k_init_module(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ s = &dev->subdevices[2];
+ if (id & II20K_ID_MOD3_EMPTY) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = ii20k_init_module(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[3];
+ if (has_dio) {
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ii20k_dio_insn_bits;
+ s->insn_config = ii20k_dio_insn_config;
+
+ /* default all channels to input */
+ ii20k_dio_config(dev, s);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static void ii20k_detach(struct comedi_device *dev)
+{
+ if (dev->mmio)
+ iounmap(dev->mmio);
+ if (dev->iobase) /* actually, a memory address */
+ release_mem_region(dev->iobase, II20K_SIZE);
+}
+
+static struct comedi_driver ii20k_driver = {
+ .driver_name = "ii_pci20kc",
+ .module = THIS_MODULE,
+ .attach = ii20k_attach,
+ .detach = ii20k_detach,
+};
+module_comedi_driver(ii20k_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/jr3_pci.c b/drivers/staging/comedi/drivers/jr3_pci.c
new file mode 100644
index 000000000..40dd2b219
--- /dev/null
+++ b/drivers/staging/comedi/drivers/jr3_pci.c
@@ -0,0 +1,828 @@
+/*
+ comedi/drivers/jr3_pci.c
+ hardware driver for JR3/PCI force sensor board
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2007 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+ * Driver: jr3_pci
+ * Description: JR3/PCI force sensor board
+ * Author: Anders Blomdell <anders.blomdell@control.lth.se>
+ * Updated: Thu, 01 Nov 2012 17:34:55 +0000
+ * Status: works
+ * Devices: [JR3] PCI force sensor board (jr3_pci)
+ *
+ * Configuration options:
+ * None
+ *
+ * Manual configuration of comedi devices is not supported by this
+ * driver; supported PCI devices are configured as comedi devices
+ * automatically.
+ *
+ * The DSP on the board requires initialization code, which can be
+ * loaded by placing it in /lib/firmware/comedi. The initialization
+ * code should be somewhere on the media you got with your card. One
+ * version is available from http://www.comedi.org in the
+ * comedi_nonfree_firmware tarball. The file is called "/*(DEBLOBBED)*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+
+#include "../comedi_pci.h"
+
+#include "jr3_pci.h"
+
+#define PCI_VENDOR_ID_JR3 0x1762
+
+enum jr3_pci_boardid {
+ BOARD_JR3_1,
+ BOARD_JR3_2,
+ BOARD_JR3_3,
+ BOARD_JR3_4,
+};
+
+struct jr3_pci_board {
+ const char *name;
+ int n_subdevs;
+};
+
+static const struct jr3_pci_board jr3_pci_boards[] = {
+ [BOARD_JR3_1] = {
+ .name = "jr3_pci_1",
+ .n_subdevs = 1,
+ },
+ [BOARD_JR3_2] = {
+ .name = "jr3_pci_2",
+ .n_subdevs = 2,
+ },
+ [BOARD_JR3_3] = {
+ .name = "jr3_pci_3",
+ .n_subdevs = 3,
+ },
+ [BOARD_JR3_4] = {
+ .name = "jr3_pci_4",
+ .n_subdevs = 4,
+ },
+};
+
+struct jr3_pci_transform {
+ struct {
+ u16 link_type;
+ s16 link_amount;
+ } link[8];
+};
+
+struct jr3_pci_poll_delay {
+ int min;
+ int max;
+};
+
+struct jr3_pci_dev_private {
+ struct jr3_t __iomem *iobase;
+ struct timer_list timer;
+};
+
+struct jr3_pci_subdev_private {
+ struct jr3_channel __iomem *channel;
+ unsigned long next_time_min;
+ unsigned long next_time_max;
+ enum { state_jr3_poll,
+ state_jr3_init_wait_for_offset,
+ state_jr3_init_transform_complete,
+ state_jr3_init_set_full_scale_complete,
+ state_jr3_init_use_offset_complete,
+ state_jr3_done
+ } state;
+ int serial_no;
+ int model_no;
+ struct {
+ int length;
+ struct comedi_krange range;
+ } range[9];
+ const struct comedi_lrange *range_table_list[8 * 7 + 2];
+ unsigned int maxdata_list[8 * 7 + 2];
+ u16 errors;
+ int retries;
+};
+
+static struct jr3_pci_poll_delay poll_delay_min_max(int min, int max)
+{
+ struct jr3_pci_poll_delay result;
+
+ result.min = min;
+ result.max = max;
+ return result;
+}
+
+static int is_complete(struct jr3_channel __iomem *channel)
+{
+ return get_s16(&channel->command_word0) == 0;
+}
+
+static void set_transforms(struct jr3_channel __iomem *channel,
+ struct jr3_pci_transform transf, short num)
+{
+ int i;
+
+ num &= 0x000f; /* Make sure that 0 <= num <= 15 */
+ for (i = 0; i < 8; i++) {
+ set_u16(&channel->transforms[num].link[i].link_type,
+ transf.link[i].link_type);
+ udelay(1);
+ set_s16(&channel->transforms[num].link[i].link_amount,
+ transf.link[i].link_amount);
+ udelay(1);
+ if (transf.link[i].link_type == end_x_form)
+ break;
+ }
+}
+
+static void use_transform(struct jr3_channel __iomem *channel,
+ short transf_num)
+{
+ set_s16(&channel->command_word0, 0x0500 + (transf_num & 0x000f));
+}
+
+static void use_offset(struct jr3_channel __iomem *channel, short offset_num)
+{
+ set_s16(&channel->command_word0, 0x0600 + (offset_num & 0x000f));
+}
+
+static void set_offset(struct jr3_channel __iomem *channel)
+{
+ set_s16(&channel->command_word0, 0x0700);
+}
+
+struct six_axis_t {
+ s16 fx;
+ s16 fy;
+ s16 fz;
+ s16 mx;
+ s16 my;
+ s16 mz;
+};
+
+static void set_full_scales(struct jr3_channel __iomem *channel,
+ struct six_axis_t full_scale)
+{
+ set_s16(&channel->full_scale.fx, full_scale.fx);
+ set_s16(&channel->full_scale.fy, full_scale.fy);
+ set_s16(&channel->full_scale.fz, full_scale.fz);
+ set_s16(&channel->full_scale.mx, full_scale.mx);
+ set_s16(&channel->full_scale.my, full_scale.my);
+ set_s16(&channel->full_scale.mz, full_scale.mz);
+ set_s16(&channel->command_word0, 0x0a00);
+}
+
+static struct six_axis_t get_min_full_scales(struct jr3_channel __iomem
+ *channel)
+{
+ struct six_axis_t result;
+
+ result.fx = get_s16(&channel->min_full_scale.fx);
+ result.fy = get_s16(&channel->min_full_scale.fy);
+ result.fz = get_s16(&channel->min_full_scale.fz);
+ result.mx = get_s16(&channel->min_full_scale.mx);
+ result.my = get_s16(&channel->min_full_scale.my);
+ result.mz = get_s16(&channel->min_full_scale.mz);
+ return result;
+}
+
+static struct six_axis_t get_max_full_scales(struct jr3_channel __iomem
+ *channel)
+{
+ struct six_axis_t result;
+
+ result.fx = get_s16(&channel->max_full_scale.fx);
+ result.fy = get_s16(&channel->max_full_scale.fy);
+ result.fz = get_s16(&channel->max_full_scale.fz);
+ result.mx = get_s16(&channel->max_full_scale.mx);
+ result.my = get_s16(&channel->max_full_scale.my);
+ result.mz = get_s16(&channel->max_full_scale.mz);
+ return result;
+}
+
+static unsigned int jr3_pci_ai_read_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan)
+{
+ struct jr3_pci_subdev_private *spriv = s->private;
+ unsigned int val = 0;
+
+ if (spriv->state != state_jr3_done)
+ return 0;
+
+ if (chan < 56) {
+ unsigned int axis = chan % 8;
+ unsigned filter = chan / 8;
+
+ switch (axis) {
+ case 0:
+ val = get_s16(&spriv->channel->filter[filter].fx);
+ break;
+ case 1:
+ val = get_s16(&spriv->channel->filter[filter].fy);
+ break;
+ case 2:
+ val = get_s16(&spriv->channel->filter[filter].fz);
+ break;
+ case 3:
+ val = get_s16(&spriv->channel->filter[filter].mx);
+ break;
+ case 4:
+ val = get_s16(&spriv->channel->filter[filter].my);
+ break;
+ case 5:
+ val = get_s16(&spriv->channel->filter[filter].mz);
+ break;
+ case 6:
+ val = get_s16(&spriv->channel->filter[filter].v1);
+ break;
+ case 7:
+ val = get_s16(&spriv->channel->filter[filter].v2);
+ break;
+ }
+ val += 0x4000;
+ } else if (chan == 56) {
+ val = get_u16(&spriv->channel->model_no);
+ } else if (chan == 57) {
+ val = get_u16(&spriv->channel->serial_no);
+ }
+
+ return val;
+}
+
+static int jr3_pci_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct jr3_pci_subdev_private *spriv = s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ u16 errors;
+ int i;
+
+ if (!spriv)
+ return -EINVAL;
+
+ errors = get_u16(&spriv->channel->errors);
+ if (spriv->state != state_jr3_done ||
+ (errors & (watch_dog | watch_dog2 | sensor_change))) {
+ /* No sensor or sensor changed */
+ if (spriv->state == state_jr3_done) {
+ /* Restart polling */
+ spriv->state = state_jr3_poll;
+ }
+ return -EAGAIN;
+ }
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = jr3_pci_ai_read_chan(dev, s, chan);
+
+ return insn->n;
+}
+
+static int jr3_pci_open(struct comedi_device *dev)
+{
+ struct jr3_pci_subdev_private *spriv;
+ struct comedi_subdevice *s;
+ int i;
+
+ dev_dbg(dev->class_dev, "jr3_pci_open\n");
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+ if (spriv)
+ dev_dbg(dev->class_dev, "serial: %p %d (%d)\n",
+ spriv, spriv->serial_no, s->index);
+ }
+ return 0;
+}
+
+static int read_idm_word(const u8 *data, size_t size, int *pos,
+ unsigned int *val)
+{
+ int result = 0;
+ int value;
+
+ if (pos && val) {
+ /* Skip over non hex */
+ for (; *pos < size && !isxdigit(data[*pos]); (*pos)++)
+ ;
+ /* Collect value */
+ *val = 0;
+ for (; *pos < size; (*pos)++) {
+ value = hex_to_bin(data[*pos]);
+ if (value >= 0) {
+ result = 1;
+ *val = (*val << 4) + value;
+ } else {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+static int jr3_check_firmware(struct comedi_device *dev,
+ const u8 *data, size_t size)
+{
+ int more = 1;
+ int pos = 0;
+
+ /*
+ * IDM file format is:
+ * { count, address, data <count> } *
+ * ffff
+ */
+ while (more) {
+ unsigned int count = 0;
+ unsigned int addr = 0;
+
+ more = more && read_idm_word(data, size, &pos, &count);
+ if (more && count == 0xffff)
+ return 0;
+
+ more = more && read_idm_word(data, size, &pos, &addr);
+ while (more && count > 0) {
+ unsigned int dummy = 0;
+
+ more = more && read_idm_word(data, size, &pos, &dummy);
+ count--;
+ }
+ }
+
+ return -ENODATA;
+}
+
+static void jr3_write_firmware(struct comedi_device *dev,
+ int subdev, const u8 *data, size_t size)
+{
+ struct jr3_pci_dev_private *devpriv = dev->private;
+ struct jr3_t __iomem *iobase = devpriv->iobase;
+ u32 __iomem *lo;
+ u32 __iomem *hi;
+ int more = 1;
+ int pos = 0;
+
+ while (more) {
+ unsigned int count = 0;
+ unsigned int addr = 0;
+
+ more = more && read_idm_word(data, size, &pos, &count);
+ if (more && count == 0xffff)
+ return;
+
+ more = more && read_idm_word(data, size, &pos, &addr);
+
+ dev_dbg(dev->class_dev, "Loading#%d %4.4x bytes at %4.4x\n",
+ subdev, count, addr);
+
+ while (more && count > 0) {
+ if (addr & 0x4000) {
+ /* 16 bit data, never seen in real life!! */
+ unsigned int data1 = 0;
+
+ more = more &&
+ read_idm_word(data, size, &pos, &data1);
+ count--;
+ /* jr3[addr + 0x20000 * pnum] = data1; */
+ } else {
+ /* Download 24 bit program */
+ unsigned int data1 = 0;
+ unsigned int data2 = 0;
+
+ lo = &iobase->channel[subdev].program_lo[addr];
+ hi = &iobase->channel[subdev].program_hi[addr];
+
+ more = more &&
+ read_idm_word(data, size, &pos, &data1);
+ more = more &&
+ read_idm_word(data, size, &pos, &data2);
+ count -= 2;
+ if (more) {
+ set_u16(lo, data1);
+ udelay(1);
+ set_u16(hi, data2);
+ udelay(1);
+ }
+ }
+ addr++;
+ }
+ }
+}
+
+static int jr3_download_firmware(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ int subdev;
+ int ret;
+
+ /* verify IDM file format */
+ ret = jr3_check_firmware(dev, data, size);
+ if (ret)
+ return ret;
+
+ /* write firmware to each subdevice */
+ for (subdev = 0; subdev < dev->n_subdevices; subdev++)
+ jr3_write_firmware(dev, subdev, data, size);
+
+ return 0;
+}
+
+static struct jr3_pci_poll_delay jr3_pci_poll_subdevice(struct comedi_subdevice *s)
+{
+ struct jr3_pci_subdev_private *spriv = s->private;
+ struct jr3_pci_poll_delay result = poll_delay_min_max(1000, 2000);
+ struct jr3_channel __iomem *channel;
+ u16 model_no;
+ u16 serial_no;
+ int errors;
+ int i;
+
+ if (!spriv)
+ return result;
+
+ channel = spriv->channel;
+ errors = get_u16(&channel->errors);
+
+ if (errors != spriv->errors)
+ spriv->errors = errors;
+
+ /* Sensor communication lost? force poll mode */
+ if (errors & (watch_dog | watch_dog2 | sensor_change))
+ spriv->state = state_jr3_poll;
+
+ switch (spriv->state) {
+ case state_jr3_poll:
+ model_no = get_u16(&channel->model_no);
+ serial_no = get_u16(&channel->serial_no);
+
+ if ((errors & (watch_dog | watch_dog2)) ||
+ model_no == 0 || serial_no == 0) {
+ /*
+ * Still no sensor, keep on polling.
+ * Since it takes up to 10 seconds for offsets to
+ * stabilize, polling each second should suffice.
+ */
+ } else {
+ spriv->retries = 0;
+ spriv->state = state_jr3_init_wait_for_offset;
+ }
+ break;
+ case state_jr3_init_wait_for_offset:
+ spriv->retries++;
+ if (spriv->retries < 10) {
+ /*
+ * Wait for offeset to stabilize
+ * (< 10 s according to manual)
+ */
+ } else {
+ struct jr3_pci_transform transf;
+
+ spriv->model_no = get_u16(&channel->model_no);
+ spriv->serial_no = get_u16(&channel->serial_no);
+
+ /* Transformation all zeros */
+ for (i = 0; i < ARRAY_SIZE(transf.link); i++) {
+ transf.link[i].link_type = (enum link_types)0;
+ transf.link[i].link_amount = 0;
+ }
+
+ set_transforms(channel, transf, 0);
+ use_transform(channel, 0);
+ spriv->state = state_jr3_init_transform_complete;
+ /* Allow 20 ms for completion */
+ result = poll_delay_min_max(20, 100);
+ }
+ break;
+ case state_jr3_init_transform_complete:
+ if (!is_complete(channel)) {
+ result = poll_delay_min_max(20, 100);
+ } else {
+ /* Set full scale */
+ struct six_axis_t min_full_scale;
+ struct six_axis_t max_full_scale;
+
+ min_full_scale = get_min_full_scales(channel);
+ max_full_scale = get_max_full_scales(channel);
+ set_full_scales(channel, max_full_scale);
+
+ spriv->state = state_jr3_init_set_full_scale_complete;
+ /* Allow 20 ms for completion */
+ result = poll_delay_min_max(20, 100);
+ }
+ break;
+ case state_jr3_init_set_full_scale_complete:
+ if (!is_complete(channel)) {
+ result = poll_delay_min_max(20, 100);
+ } else {
+ struct force_array __iomem *fs = &channel->full_scale;
+
+ /* Use ranges in kN or we will overflow around 2000N! */
+ spriv->range[0].range.min = -get_s16(&fs->fx) * 1000;
+ spriv->range[0].range.max = get_s16(&fs->fx) * 1000;
+ spriv->range[1].range.min = -get_s16(&fs->fy) * 1000;
+ spriv->range[1].range.max = get_s16(&fs->fy) * 1000;
+ spriv->range[2].range.min = -get_s16(&fs->fz) * 1000;
+ spriv->range[2].range.max = get_s16(&fs->fz) * 1000;
+ spriv->range[3].range.min = -get_s16(&fs->mx) * 100;
+ spriv->range[3].range.max = get_s16(&fs->mx) * 100;
+ spriv->range[4].range.min = -get_s16(&fs->my) * 100;
+ spriv->range[4].range.max = get_s16(&fs->my) * 100;
+ spriv->range[5].range.min = -get_s16(&fs->mz) * 100;
+ /* the next five are questionable */
+ spriv->range[5].range.max = get_s16(&fs->mz) * 100;
+ spriv->range[6].range.min = -get_s16(&fs->v1) * 100;
+ spriv->range[6].range.max = get_s16(&fs->v1) * 100;
+ spriv->range[7].range.min = -get_s16(&fs->v2) * 100;
+ spriv->range[7].range.max = get_s16(&fs->v2) * 100;
+ spriv->range[8].range.min = 0;
+ spriv->range[8].range.max = 65535;
+
+ use_offset(channel, 0);
+ spriv->state = state_jr3_init_use_offset_complete;
+ /* Allow 40 ms for completion */
+ result = poll_delay_min_max(40, 100);
+ }
+ break;
+ case state_jr3_init_use_offset_complete:
+ if (!is_complete(channel)) {
+ result = poll_delay_min_max(20, 100);
+ } else {
+ set_s16(&channel->offsets.fx, 0);
+ set_s16(&channel->offsets.fy, 0);
+ set_s16(&channel->offsets.fz, 0);
+ set_s16(&channel->offsets.mx, 0);
+ set_s16(&channel->offsets.my, 0);
+ set_s16(&channel->offsets.mz, 0);
+
+ set_offset(channel);
+
+ spriv->state = state_jr3_done;
+ }
+ break;
+ case state_jr3_done:
+ result = poll_delay_min_max(10000, 20000);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+static void jr3_pci_poll_dev(unsigned long data)
+{
+ struct comedi_device *dev = (struct comedi_device *)data;
+ struct jr3_pci_dev_private *devpriv = dev->private;
+ struct jr3_pci_subdev_private *spriv;
+ struct comedi_subdevice *s;
+ unsigned long flags;
+ unsigned long now;
+ int delay;
+ int i;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ delay = 1000;
+ now = jiffies;
+
+ /* Poll all channels that are ready to be polled */
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+
+ if (now > spriv->next_time_min) {
+ struct jr3_pci_poll_delay sub_delay;
+
+ sub_delay = jr3_pci_poll_subdevice(s);
+
+ spriv->next_time_min = jiffies +
+ msecs_to_jiffies(sub_delay.min);
+ spriv->next_time_max = jiffies +
+ msecs_to_jiffies(sub_delay.max);
+
+ if (sub_delay.max && sub_delay.max < delay)
+ /*
+ * Wake up as late as possible ->
+ * poll as many channels as possible at once.
+ */
+ delay = sub_delay.max;
+ }
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ devpriv->timer.expires = jiffies + msecs_to_jiffies(delay);
+ add_timer(&devpriv->timer);
+}
+
+static struct jr3_pci_subdev_private *
+jr3_pci_alloc_spriv(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct jr3_pci_dev_private *devpriv = dev->private;
+ struct jr3_pci_subdev_private *spriv;
+ int j;
+ int k;
+
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return NULL;
+
+ spriv->channel = &devpriv->iobase->channel[s->index].data;
+
+ for (j = 0; j < 8; j++) {
+ spriv->range[j].length = 1;
+ spriv->range[j].range.min = -1000000;
+ spriv->range[j].range.max = 1000000;
+
+ for (k = 0; k < 7; k++) {
+ spriv->range_table_list[j + k * 8] =
+ (struct comedi_lrange *)&spriv->range[j];
+ spriv->maxdata_list[j + k * 8] = 0x7fff;
+ }
+ }
+ spriv->range[8].length = 1;
+ spriv->range[8].range.min = 0;
+ spriv->range[8].range.max = 65536;
+
+ spriv->range_table_list[56] = (struct comedi_lrange *)&spriv->range[8];
+ spriv->range_table_list[57] = (struct comedi_lrange *)&spriv->range[8];
+ spriv->maxdata_list[56] = 0xffff;
+ spriv->maxdata_list[57] = 0xffff;
+
+ dev_dbg(dev->class_dev, "p->channel %p %p (%tx)\n",
+ spriv->channel, devpriv->iobase,
+ ((char __iomem *)spriv->channel -
+ (char __iomem *)devpriv->iobase));
+
+ return spriv;
+}
+
+static int jr3_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ static const struct jr3_pci_board *board;
+ struct jr3_pci_dev_private *devpriv;
+ struct jr3_pci_subdev_private *spriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ if (sizeof(struct jr3_channel) != 0xc00) {
+ dev_err(dev->class_dev,
+ "sizeof(struct jr3_channel) = %x [expected %x]\n",
+ (unsigned)sizeof(struct jr3_channel), 0xc00);
+ return -EINVAL;
+ }
+
+ if (context < ARRAY_SIZE(jr3_pci_boards))
+ board = &jr3_pci_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->iobase = pci_ioremap_bar(pcidev, 0);
+ if (!devpriv->iobase)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, board->n_subdevs);
+ if (ret)
+ return ret;
+
+ dev->open = jr3_pci_open;
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8 * 7 + 2;
+ s->insn_read = jr3_pci_ai_insn_read;
+
+ spriv = jr3_pci_alloc_spriv(dev, s);
+ if (spriv) {
+ /* Channel specific range and maxdata */
+ s->range_table_list = spriv->range_table_list;
+ s->maxdata_list = spriv->maxdata_list;
+ }
+ }
+
+ /* Reset DSP card */
+ writel(0, &devpriv->iobase->channel[0].reset);
+
+ ret = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ "/*(DEBLOBBED)*/",
+ jr3_download_firmware, 0);
+ dev_dbg(dev->class_dev, "Firmare load %d\n", ret);
+ if (ret < 0)
+ return ret;
+ /*
+ * TODO: use firmware to load preferred offset tables. Suggested
+ * format:
+ * model serial Fx Fy Fz Mx My Mz\n
+ *
+ * comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ * "comedi/jr3_offsets_table",
+ * jr3_download_firmware, 1);
+ */
+
+ /*
+ * It takes a few milliseconds for software to settle as much as we
+ * can read firmware version
+ */
+ msleep_interruptible(25);
+ for (i = 0; i < 0x18; i++) {
+ dev_dbg(dev->class_dev, "%c\n",
+ get_u16(&devpriv->iobase->channel[0].
+ data.copyright[i]) >> 8);
+ }
+
+ /* Start card timer */
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+
+ spriv->next_time_min = jiffies + msecs_to_jiffies(500);
+ spriv->next_time_max = jiffies + msecs_to_jiffies(2000);
+ }
+
+ setup_timer(&devpriv->timer, jr3_pci_poll_dev, (unsigned long)dev);
+ devpriv->timer.expires = jiffies + msecs_to_jiffies(1000);
+ add_timer(&devpriv->timer);
+
+ return 0;
+}
+
+static void jr3_pci_detach(struct comedi_device *dev)
+{
+ struct jr3_pci_dev_private *devpriv = dev->private;
+
+ if (devpriv) {
+ del_timer_sync(&devpriv->timer);
+
+ if (devpriv->iobase)
+ iounmap(devpriv->iobase);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver jr3_pci_driver = {
+ .driver_name = "jr3_pci",
+ .module = THIS_MODULE,
+ .auto_attach = jr3_pci_auto_attach,
+ .detach = jr3_pci_detach,
+};
+
+static int jr3_pci_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &jr3_pci_driver, id->driver_data);
+}
+
+static const struct pci_device_id jr3_pci_pci_table[] = {
+ { PCI_VDEVICE(JR3, 0x1111), BOARD_JR3_1 },
+ { PCI_VDEVICE(JR3, 0x3111), BOARD_JR3_1 },
+ { PCI_VDEVICE(JR3, 0x3112), BOARD_JR3_2 },
+ { PCI_VDEVICE(JR3, 0x3113), BOARD_JR3_3 },
+ { PCI_VDEVICE(JR3, 0x3114), BOARD_JR3_4 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, jr3_pci_pci_table);
+
+static struct pci_driver jr3_pci_pci_driver = {
+ .name = "jr3_pci",
+ .id_table = jr3_pci_pci_table,
+ .probe = jr3_pci_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(jr3_pci_driver, jr3_pci_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
+/*(DEBLOBBED)*/
diff --git a/drivers/staging/comedi/drivers/jr3_pci.h b/drivers/staging/comedi/drivers/jr3_pci.h
new file mode 100644
index 000000000..356811def
--- /dev/null
+++ b/drivers/staging/comedi/drivers/jr3_pci.h
@@ -0,0 +1,682 @@
+/* Helper types to take care of the fact that the DSP card memory
+ * is 16 bits, but aligned on a 32 bit PCI boundary
+ */
+
+static inline u16 get_u16(const u32 __iomem *p)
+{
+ return (u16)readl(p);
+}
+
+static inline void set_u16(u32 __iomem *p, u16 val)
+{
+ writel(val, p);
+}
+
+static inline s16 get_s16(const s32 __iomem *p)
+{
+ return (s16)readl(p);
+}
+
+static inline void set_s16(s32 __iomem *p, s16 val)
+{
+ writel(val, p);
+}
+
+/* The raw data is stored in a format which facilitates rapid
+ * processing by the JR3 DSP chip. The raw_channel structure shows the
+ * format for a single channel of data. Each channel takes four,
+ * two-byte words.
+ *
+ * Raw_time is an unsigned integer which shows the value of the JR3
+ * DSP's internal clock at the time the sample was received. The clock
+ * runs at 1/10 the JR3 DSP cycle time. JR3's slowest DSP runs at 10
+ * Mhz. At 10 Mhz raw_time would therefore clock at 1 Mhz.
+ *
+ * Raw_data is the raw data received directly from the sensor. The
+ * sensor data stream is capable of representing 16 different
+ * channels. Channel 0 shows the excitation voltage at the sensor. It
+ * is used to regulate the voltage over various cable lengths.
+ * Channels 1-6 contain the coupled force data Fx through Mz. Channel
+ * 7 contains the sensor's calibration data. The use of channels 8-15
+ * varies with different sensors.
+ */
+
+struct raw_channel {
+ u32 raw_time;
+ s32 raw_data;
+ s32 reserved[2];
+};
+
+/* The force_array structure shows the layout for the decoupled and
+ * filtered force data.
+ */
+struct force_array {
+ s32 fx;
+ s32 fy;
+ s32 fz;
+ s32 mx;
+ s32 my;
+ s32 mz;
+ s32 v1;
+ s32 v2;
+};
+
+/* The six_axis_array structure shows the layout for the offsets and
+ * the full scales.
+ */
+struct six_axis_array {
+ s32 fx;
+ s32 fy;
+ s32 fz;
+ s32 mx;
+ s32 my;
+ s32 mz;
+};
+
+/* VECT_BITS */
+/* The vect_bits structure shows the layout for indicating
+ * which axes to use in computing the vectors. Each bit signifies
+ * selection of a single axis. The V1x axis bit corresponds to a hex
+ * value of 0x0001 and the V2z bit corresponds to a hex value of
+ * 0x0020. Example: to specify the axes V1x, V1y, V2x, and V2z the
+ * pattern would be 0x002b. Vector 1 defaults to a force vector and
+ * vector 2 defaults to a moment vector. It is possible to change one
+ * or the other so that two force vectors or two moment vectors are
+ * calculated. Setting the changeV1 bit or the changeV2 bit will
+ * change that vector to be the opposite of its default. Therefore to
+ * have two force vectors, set changeV1 to 1.
+ */
+
+/* vect_bits appears to be unused at this time */
+enum {
+ fx = 0x0001,
+ fy = 0x0002,
+ fz = 0x0004,
+ mx = 0x0008,
+ my = 0x0010,
+ mz = 0x0020,
+ changeV2 = 0x0040,
+ changeV1 = 0x0080
+};
+
+/* WARNING_BITS */
+/* The warning_bits structure shows the bit pattern for the warning
+ * word. The bit fields are shown from bit 0 (lsb) to bit 15 (msb).
+ */
+
+/* XX_NEAR_SET */
+/* The xx_near_sat bits signify that the indicated axis has reached or
+ * exceeded the near saturation value.
+ */
+
+enum {
+ fx_near_sat = 0x0001,
+ fy_near_sat = 0x0002,
+ fz_near_sat = 0x0004,
+ mx_near_sat = 0x0008,
+ my_near_sat = 0x0010,
+ mz_near_sat = 0x0020
+};
+
+/* ERROR_BITS */
+/* XX_SAT */
+/* MEMORY_ERROR */
+/* SENSOR_CHANGE */
+
+/* The error_bits structure shows the bit pattern for the error word.
+ * The bit fields are shown from bit 0 (lsb) to bit 15 (msb). The
+ * xx_sat bits signify that the indicated axis has reached or exceeded
+ * the saturation value. The memory_error bit indicates that a problem
+ * was detected in the on-board RAM during the power-up
+ * initialization. The sensor_change bit indicates that a sensor other
+ * than the one originally plugged in has passed its CRC check. This
+ * bit latches, and must be reset by the user.
+ *
+ */
+
+/* SYSTEM_BUSY */
+
+/* The system_busy bit indicates that the JR3 DSP is currently busy
+ * and is not calculating force data. This occurs when a new
+ * coordinate transformation, or new sensor full scale is set by the
+ * user. A very fast system using the force data for feedback might
+ * become unstable during the approximately 4 ms needed to accomplish
+ * these calculations. This bit will also become active when a new
+ * sensor is plugged in and the system needs to recalculate the
+ * calibration CRC.
+ */
+
+/* CAL_CRC_BAD */
+
+/* The cal_crc_bad bit indicates that the calibration CRC has not
+ * calculated to zero. CRC is short for cyclic redundancy code. It is
+ * a method for determining the integrity of messages in data
+ * communication. The calibration data stored inside the sensor is
+ * transmitted to the JR3 DSP along with the sensor data. The
+ * calibration data has a CRC attached to the end of it, to assist in
+ * determining the completeness and integrity of the calibration data
+ * received from the sensor. There are two reasons the CRC may not
+ * have calculated to zero. The first is that all the calibration data
+ * has not yet been received, the second is that the calibration data
+ * has been corrupted. A typical sensor transmits the entire contents
+ * of its calibration matrix over 30 times a second. Therefore, if
+ * this bit is not zero within a couple of seconds after the sensor
+ * has been plugged in, there is a problem with the sensor's
+ * calibration data.
+ */
+
+/* WATCH_DOG */
+/* WATCH_DOG2 */
+
+/* The watch_dog and watch_dog2 bits are sensor, not processor, watch
+ * dog bits. Watch_dog indicates that the sensor data line seems to be
+ * acting correctly, while watch_dog2 indicates that sensor data and
+ * clock are being received. It is possible for watch_dog2 to go off
+ * while watch_dog does not. This would indicate an improper clock
+ * signal, while data is acting correctly. If either watch dog barks,
+ * the sensor data is not being received correctly.
+ */
+
+enum error_bits_t {
+ fx_sat = 0x0001,
+ fy_sat = 0x0002,
+ fz_sat = 0x0004,
+ mx_sat = 0x0008,
+ my_sat = 0x0010,
+ mz_sat = 0x0020,
+ memory_error = 0x0400,
+ sensor_change = 0x0800,
+ system_busy = 0x1000,
+ cal_crc_bad = 0x2000,
+ watch_dog2 = 0x4000,
+ watch_dog = 0x8000
+};
+
+/* THRESH_STRUCT */
+
+/* This structure shows the layout for a single threshold packet inside of a
+ * load envelope. Each load envelope can contain several threshold structures.
+ * 1. data_address contains the address of the data for that threshold. This
+ * includes filtered, unfiltered, raw, rate, counters, error and warning data
+ * 2. threshold is the is the value at which, if data is above or below, the
+ * bits will be set ... (pag.24).
+ * 3. bit_pattern contains the bits that will be set if the threshold value is
+ * met or exceeded.
+ */
+
+struct thresh_struct {
+ s32 data_address;
+ s32 threshold;
+ s32 bit_pattern;
+};
+
+/* LE_STRUCT */
+
+/* Layout of a load enveloped packet. Four thresholds are showed ... for more
+ * see manual (pag.25)
+ * 1. latch_bits is a bit pattern that show which bits the user wants to latch.
+ * The latched bits will not be reset once the threshold which set them is
+ * no longer true. In that case the user must reset them using the reset_bit
+ * command.
+ * 2. number_of_xx_thresholds specify how many GE/LE threshold there are.
+ */
+struct le_struct {
+ s32 latch_bits;
+ s32 number_of_ge_thresholds;
+ s32 number_of_le_thresholds;
+ struct thresh_struct thresholds[4];
+ s32 reserved;
+};
+
+/* LINK_TYPES */
+/* Link types is an enumerated value showing the different possible transform
+ * link types.
+ * 0 - end transform packet
+ * 1 - translate along X axis (TX)
+ * 2 - translate along Y axis (TY)
+ * 3 - translate along Z axis (TZ)
+ * 4 - rotate about X axis (RX)
+ * 5 - rotate about Y axis (RY)
+ * 6 - rotate about Z axis (RZ)
+ * 7 - negate all axes (NEG)
+ */
+
+enum link_types {
+ end_x_form,
+ tx,
+ ty,
+ tz,
+ rx,
+ ry,
+ rz,
+ neg
+};
+
+/* TRANSFORM */
+/* Structure used to describe a transform. */
+struct intern_transform {
+ struct {
+ u32 link_type;
+ s32 link_amount;
+ } link[8];
+};
+
+/* JR3 force/torque sensor data definition. For more information see sensor
+ * and hardware manuals.
+ */
+
+struct jr3_channel {
+ /* Raw_channels is the area used to store the raw data coming from */
+ /* the sensor. */
+
+ struct raw_channel raw_channels[16]; /* offset 0x0000 */
+
+ /* Copyright is a null terminated ASCII string containing the JR3 */
+ /* copyright notice. */
+
+ u32 copyright[0x0018]; /* offset 0x0040 */
+ s32 reserved1[0x0008]; /* offset 0x0058 */
+
+ /* Shunts contains the sensor shunt readings. Some JR3 sensors have
+ * the ability to have their gains adjusted. This allows the
+ * hardware full scales to be adjusted to potentially allow
+ * better resolution or dynamic range. For sensors that have
+ * this ability, the gain of each sensor channel is measured at
+ * the time of calibration using a shunt resistor. The shunt
+ * resistor is placed across one arm of the resistor bridge, and
+ * the resulting change in the output of that channel is
+ * measured. This measurement is called the shunt reading, and
+ * is recorded here. If the user has changed the gain of the //
+ * sensor, and made new shunt measurements, those shunt
+ * measurements can be placed here. The JR3 DSP will then scale
+ * the calibration matrix such so that the gains are again
+ * proper for the indicated shunt readings. If shunts is 0, then
+ * the sensor cannot have its gain changed. For details on
+ * changing the sensor gain, and making shunts readings, please
+ * see the sensor manual. To make these values take effect the
+ * user must call either command (5) use transform # (pg. 33) or
+ * command (10) set new full scales (pg. 38).
+ */
+
+ struct six_axis_array shunts; /* offset 0x0060 */
+ s32 reserved2[2]; /* offset 0x0066 */
+
+ /* Default_FS contains the full scale that is used if the user does */
+ /* not set a full scale. */
+
+ struct six_axis_array default_FS; /* offset 0x0068 */
+ s32 reserved3; /* offset 0x006e */
+
+ /* Load_envelope_num is the load envelope number that is currently
+ * in use. This value is set by the user after one of the load
+ * envelopes has been initialized.
+ */
+
+ s32 load_envelope_num; /* offset 0x006f */
+
+ /* Min_full_scale is the recommend minimum full scale. */
+
+ /* These values in conjunction with max_full_scale (pg. 9) helps
+ * determine the appropriate value for setting the full scales. The
+ * software allows the user to set the sensor full scale to an
+ * arbitrary value. But setting the full scales has some hazards. If
+ * the full scale is set too low, the data will saturate
+ * prematurely, and dynamic range will be lost. If the full scale is
+ * set too high, then resolution is lost as the data is shifted to
+ * the right and the least significant bits are lost. Therefore the
+ * maximum full scale is the maximum value at which no resolution is
+ * lost, and the minimum full scale is the value at which the data
+ * will not saturate prematurely. These values are calculated
+ * whenever a new coordinate transformation is calculated. It is
+ * possible for the recommended maximum to be less than the
+ * recommended minimum. This comes about primarily when using
+ * coordinate translations. If this is the case, it means that any
+ * full scale selection will be a compromise between dynamic range
+ * and resolution. It is usually recommended to compromise in favor
+ * of resolution which means that the recommend maximum full scale
+ * should be chosen.
+ *
+ * WARNING: Be sure that the full scale is no less than 0.4% of the
+ * recommended minimum full scale. Full scales below this value will
+ * cause erroneous results.
+ */
+
+ struct six_axis_array min_full_scale; /* offset 0x0070 */
+ s32 reserved4; /* offset 0x0076 */
+
+ /* Transform_num is the transform number that is currently in use.
+ * This value is set by the JR3 DSP after the user has used command
+ * (5) use transform # (pg. 33).
+ */
+
+ s32 transform_num; /* offset 0x0077 */
+
+ /* Max_full_scale is the recommended maximum full scale. See */
+ /* min_full_scale (pg. 9) for more details. */
+
+ struct six_axis_array max_full_scale; /* offset 0x0078 */
+ s32 reserved5; /* offset 0x007e */
+
+ /* Peak_address is the address of the data which will be monitored
+ * by the peak routine. This value is set by the user. The peak
+ * routine will monitor any 8 contiguous addresses for peak values.
+ * (ex. to watch filter3 data for peaks, set this value to 0x00a8).
+ */
+
+ s32 peak_address; /* offset 0x007f */
+
+ /* Full_scale is the sensor full scales which are currently in use.
+ * Decoupled and filtered data is scaled so that +/- 16384 is equal
+ * to the full scales. The engineering units used are indicated by
+ * the units value discussed on page 16. The full scales for Fx, Fy,
+ * Fz, Mx, My and Mz can be written by the user prior to calling
+ * command (10) set new full scales (pg. 38). The full scales for V1
+ * and V2 are set whenever the full scales are changed or when the
+ * axes used to calculate the vectors are changed. The full scale of
+ * V1 and V2 will always be equal to the largest full scale of the
+ * axes used for each vector respectively.
+ */
+
+ struct force_array full_scale; /* offset 0x0080 */
+
+ /* Offsets contains the sensor offsets. These values are subtracted from
+ * the sensor data to obtain the decoupled data. The offsets are set a
+ * few seconds (< 10) after the calibration data has been received.
+ * They are set so that the output data will be zero. These values
+ * can be written as well as read. The JR3 DSP will use the values
+ * written here within 2 ms of being written. To set future
+ * decoupled data to zero, add these values to the current decoupled
+ * data values and place the sum here. The JR3 DSP will change these
+ * values when a new transform is applied. So if the offsets are
+ * such that FX is 5 and all other values are zero, after rotating
+ * about Z by 90 degrees, FY would be 5 and all others would be zero.
+ */
+
+ struct six_axis_array offsets; /* offset 0x0088 */
+
+ /* Offset_num is the number of the offset currently in use. This
+ * value is set by the JR3 DSP after the user has executed the use
+ * offset # command (pg. 34). It can vary between 0 and 15.
+ */
+
+ s32 offset_num; /* offset 0x008e */
+
+ /* Vect_axes is a bit map showing which of the axes are being used
+ * in the vector calculations. This value is set by the JR3 DSP
+ * after the user has executed the set vector axes command (pg. 37).
+ */
+
+ u32 vect_axes; /* offset 0x008f */
+
+ /* Filter0 is the decoupled, unfiltered data from the JR3 sensor.
+ * This data has had the offsets removed.
+ *
+ * These force_arrays hold the filtered data. The decoupled data is
+ * passed through cascaded low pass filters. Each succeeding filter
+ * has a cutoff frequency of 1/4 of the preceding filter. The cutoff
+ * frequency of filter1 is 1/16 of the sample rate from the sensor.
+ * For a typical sensor with a sample rate of 8 kHz, the cutoff
+ * frequency of filter1 would be 500 Hz. The following filters would
+ * cutoff at 125 Hz, 31.25 Hz, 7.813 Hz, 1.953 Hz and 0.4883 Hz.
+ */
+
+ struct force_array filter[7]; /* offset 0x0090,
+ offset 0x0098,
+ offset 0x00a0,
+ offset 0x00a8,
+ offset 0x00b0,
+ offset 0x00b8 ,
+ offset 0x00c0 */
+
+ /* Rate_data is the calculated rate data. It is a first derivative
+ * calculation. It is calculated at a frequency specified by the
+ * variable rate_divisor (pg. 12). The data on which the rate is
+ * calculated is specified by the variable rate_address (pg. 12).
+ */
+
+ struct force_array rate_data; /* offset 0x00c8 */
+
+ /* Minimum_data & maximum_data are the minimum and maximum (peak)
+ * data values. The JR3 DSP can monitor any 8 contiguous data items
+ * for minimums and maximums at full sensor bandwidth. This area is
+ * only updated at user request. This is done so that the user does
+ * not miss any peaks. To read the data, use either the read peaks
+ * command (pg. 40), or the read and reset peaks command (pg. 39).
+ * The address of the data to watch for peaks is stored in the
+ * variable peak_address (pg. 10). Peak data is lost when executing
+ * a coordinate transformation or a full scale change. Peak data is
+ * also lost when plugging in a new sensor.
+ */
+
+ struct force_array minimum_data; /* offset 0x00d0 */
+ struct force_array maximum_data; /* offset 0x00d8 */
+
+ /* Near_sat_value & sat_value contain the value used to determine if
+ * the raw sensor is saturated. Because of decoupling and offset
+ * removal, it is difficult to tell from the processed data if the
+ * sensor is saturated. These values, in conjunction with the error
+ * and warning words (pg. 14), provide this critical information.
+ * These two values may be set by the host processor. These values
+ * are positive signed values, since the saturation logic uses the
+ * absolute values of the raw data. The near_sat_value defaults to
+ * approximately 80% of the ADC's full scale, which is 26214, while
+ * sat_value defaults to the ADC's full scale:
+ *
+ * sat_value = 32768 - 2^(16 - ADC bits)
+ */
+
+ s32 near_sat_value; /* offset 0x00e0 */
+ s32 sat_value; /* offset 0x00e1 */
+
+ /* Rate_address, rate_divisor & rate_count contain the data used to
+ * control the calculations of the rates. Rate_address is the
+ * address of the data used for the rate calculation. The JR3 DSP
+ * will calculate rates for any 8 contiguous values (ex. to
+ * calculate rates for filter3 data set rate_address to 0x00a8).
+ * Rate_divisor is how often the rate is calculated. If rate_divisor
+ * is 1, the rates are calculated at full sensor bandwidth. If
+ * rate_divisor is 200, rates are calculated every 200 samples.
+ * Rate_divisor can be any value between 1 and 65536. Set
+ * rate_divisor to 0 to calculate rates every 65536 samples.
+ * Rate_count starts at zero and counts until it equals
+ * rate_divisor, at which point the rates are calculated, and
+ * rate_count is reset to 0. When setting a new rate divisor, it is
+ * a good idea to set rate_count to one less than rate divisor. This
+ * will minimize the time necessary to start the rate calculations.
+ */
+
+ s32 rate_address; /* offset 0x00e2 */
+ u32 rate_divisor; /* offset 0x00e3 */
+ u32 rate_count; /* offset 0x00e4 */
+
+ /* Command_word2 through command_word0 are the locations used to
+ * send commands to the JR3 DSP. Their usage varies with the command
+ * and is detailed later in the Command Definitions section (pg.
+ * 29). In general the user places values into various memory
+ * locations, and then places the command word into command_word0.
+ * The JR3 DSP will process the command and place a 0 into
+ * command_word0 to indicate successful completion. Alternatively
+ * the JR3 DSP will place a negative number into command_word0 to
+ * indicate an error condition. Please note the command locations
+ * are numbered backwards. (I.E. command_word2 comes before
+ * command_word1).
+ */
+
+ s32 command_word2; /* offset 0x00e5 */
+ s32 command_word1; /* offset 0x00e6 */
+ s32 command_word0; /* offset 0x00e7 */
+
+ /* Count1 through count6 are unsigned counters which are incremented
+ * every time the matching filters are calculated. Filter1 is
+ * calculated at the sensor data bandwidth. So this counter would
+ * increment at 8 kHz for a typical sensor. The rest of the counters
+ * are incremented at 1/4 the interval of the counter immediately
+ * preceding it, so they would count at 2 kHz, 500 Hz, 125 Hz etc.
+ * These counters can be used to wait for data. Each time the
+ * counter changes, the corresponding data set can be sampled, and
+ * this will insure that the user gets each sample, once, and only
+ * once.
+ */
+
+ u32 count1; /* offset 0x00e8 */
+ u32 count2; /* offset 0x00e9 */
+ u32 count3; /* offset 0x00ea */
+ u32 count4; /* offset 0x00eb */
+ u32 count5; /* offset 0x00ec */
+ u32 count6; /* offset 0x00ed */
+
+ /* Error_count is a running count of data reception errors. If this
+ * counter is changing rapidly, it probably indicates a bad sensor
+ * cable connection or other hardware problem. In most installations
+ * error_count should not change at all. But it is possible in an
+ * extremely noisy environment to experience occasional errors even
+ * without a hardware problem. If the sensor is well grounded, this
+ * is probably unavoidable in these environments. On the occasions
+ * where this counter counts a bad sample, that sample is ignored.
+ */
+
+ u32 error_count; /* offset 0x00ee */
+
+ /* Count_x is a counter which is incremented every time the JR3 DSP
+ * searches its job queues and finds nothing to do. It indicates the
+ * amount of idle time the JR3 DSP has available. It can also be
+ * used to determine if the JR3 DSP is alive. See the Performance
+ * Issues section on pg. 49 for more details.
+ */
+
+ u32 count_x; /* offset 0x00ef */
+
+ /* Warnings & errors contain the warning and error bits
+ * respectively. The format of these two words is discussed on page
+ * 21 under the headings warnings_bits and error_bits.
+ */
+
+ u32 warnings; /* offset 0x00f0 */
+ u32 errors; /* offset 0x00f1 */
+
+ /* Threshold_bits is a word containing the bits that are set by the
+ * load envelopes. See load_envelopes (pg. 17) and thresh_struct
+ * (pg. 23) for more details.
+ */
+
+ s32 threshold_bits; /* offset 0x00f2 */
+
+ /* Last_crc is the value that shows the actual calculated CRC. CRC
+ * is short for cyclic redundancy code. It should be zero. See the
+ * description for cal_crc_bad (pg. 21) for more information.
+ */
+
+ s32 last_CRC; /* offset 0x00f3 */
+
+ /* EEProm_ver_no contains the version number of the sensor EEProm.
+ * EEProm version numbers can vary between 0 and 255.
+ * Software_ver_no contains the software version number. Version
+ * 3.02 would be stored as 302.
+ */
+
+ s32 eeprom_ver_no; /* offset 0x00f4 */
+ s32 software_ver_no; /* offset 0x00f5 */
+
+ /* Software_day & software_year are the release date of the software
+ * the JR3 DSP is currently running. Day is the day of the year,
+ * with January 1 being 1, and December 31, being 365 for non leap
+ * years.
+ */
+
+ s32 software_day; /* offset 0x00f6 */
+ s32 software_year; /* offset 0x00f7 */
+
+ /* Serial_no & model_no are the two values which uniquely identify a
+ * sensor. This model number does not directly correspond to the JR3
+ * model number, but it will provide a unique identifier for
+ * different sensor configurations.
+ */
+
+ u32 serial_no; /* offset 0x00f8 */
+ u32 model_no; /* offset 0x00f9 */
+
+ /* Cal_day & cal_year are the sensor calibration date. Day is the
+ * day of the year, with January 1 being 1, and December 31, being
+ * 366 for leap years.
+ */
+
+ s32 cal_day; /* offset 0x00fa */
+ s32 cal_year; /* offset 0x00fb */
+
+ /* Units is an enumerated read only value defining the engineering
+ * units used in the sensor full scale. The meanings of particular
+ * values are discussed in the section detailing the force_units
+ * structure on page 22. The engineering units are setto customer
+ * specifications during sensor manufacture and cannot be changed by
+ * writing to Units.
+ *
+ * Bits contains the number of bits of resolution of the ADC
+ * currently in use.
+ *
+ * Channels is a bit field showing which channels the current sensor
+ * is capable of sending. If bit 0 is active, this sensor can send
+ * channel 0, if bit 13 is active, this sensor can send channel 13,
+ * etc. This bit can be active, even if the sensor is not currently
+ * sending this channel. Some sensors are configurable as to which
+ * channels to send, and this field only contains information on the
+ * channels available to send, not on the current configuration. To
+ * find which channels are currently being sent, monitor the
+ * Raw_time fields (pg. 19) in the raw_channels array (pg. 7). If
+ * the time is changing periodically, then that channel is being
+ * received.
+ */
+
+ u32 units; /* offset 0x00fc */
+ s32 bits; /* offset 0x00fd */
+ s32 channels; /* offset 0x00fe */
+
+ /* Thickness specifies the overall thickness of the sensor from
+ * flange to flange. The engineering units for this value are
+ * contained in units (pg. 16). The sensor calibration is relative
+ * to the center of the sensor. This value allows easy coordinate
+ * transformation from the center of the sensor to either flange.
+ */
+
+ s32 thickness; /* offset 0x00ff */
+
+ /* Load_envelopes is a table containing the load envelope
+ * descriptions. There are 16 possible load envelope slots in the
+ * table. The slots are on 16 word boundaries and are numbered 0-15.
+ * Each load envelope needs to start at the beginning of a slot but
+ * need not be fully contained in that slot. That is to say that a
+ * single load envelope can be larger than a single slot. The
+ * software has been tested and ran satisfactorily with 50
+ * thresholds active. A single load envelope this large would take
+ * up 5 of the 16 slots. The load envelope data is laid out in an
+ * order that is most efficient for the JR3 DSP. The structure is
+ * detailed later in the section showing the definition of the
+ * le_struct structure (pg. 23).
+ */
+
+ struct le_struct load_envelopes[0x10]; /* offset 0x0100 */
+
+ /* Transforms is a table containing the transform descriptions.
+ * There are 16 possible transform slots in the table. The slots are
+ * on 16 word boundaries and are numbered 0-15. Each transform needs
+ * to start at the beginning of a slot but need not be fully
+ * contained in that slot. That is to say that a single transform
+ * can be larger than a single slot. A transform is 2 * no of links
+ * + 1 words in length. So a single slot can contain a transform
+ * with 7 links. Two slots can contain a transform that is 15 links.
+ * The layout is detailed later in the section showing the
+ * definition of the transform structure (pg. 26).
+ */
+
+ struct intern_transform transforms[0x10]; /* offset 0x0200 */
+};
+
+struct jr3_t {
+ struct {
+ u32 program_lo[0x4000]; /* 0x00000 - 0x10000 */
+ struct jr3_channel data; /* 0x10000 - 0x10c00 */
+ char pad2[0x30000 - 0x00c00]; /* 0x10c00 - 0x40000 */
+ u32 program_hi[0x8000]; /* 0x40000 - 0x60000 */
+ u32 reset; /* 0x60000 - 0x60004 */
+ char pad3[0x20000 - 0x00004]; /* 0x60004 - 0x80000 */
+ } channel[4];
+};
diff --git a/drivers/staging/comedi/drivers/ke_counter.c b/drivers/staging/comedi/drivers/ke_counter.c
new file mode 100644
index 000000000..dc642edf4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ke_counter.c
@@ -0,0 +1,240 @@
+/*
+ * ke_counter.c
+ * Comedi driver for Kolter-Electronic PCI Counter 1 Card
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ke_counter
+ * Description: Driver for Kolter Electronic Counter Card
+ * Devices: [Kolter Electronic] PCI Counter Card (ke_counter)
+ * Author: Michael Hillmann
+ * Updated: Mon, 14 Apr 2008 15:42:42 +0100
+ * Status: tested
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * PCI BAR 0 Register I/O map
+ */
+#define KE_RESET_REG(x) (0x00 + ((x) * 0x20))
+#define KE_LATCH_REG(x) (0x00 + ((x) * 0x20))
+#define KE_LSB_REG(x) (0x04 + ((x) * 0x20))
+#define KE_MID_REG(x) (0x08 + ((x) * 0x20))
+#define KE_MSB_REG(x) (0x0c + ((x) * 0x20))
+#define KE_SIGN_REG(x) (0x10 + ((x) * 0x20))
+#define KE_OSC_SEL_REG 0xf8
+#define KE_OSC_SEL_EXT (1 << 0)
+#define KE_OSC_SEL_4MHZ (2 << 0)
+#define KE_OSC_SEL_20MHZ (3 << 0)
+#define KE_DO_REG 0xfc
+
+static int ke_counter_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[0];
+
+ /* Order matters */
+ outb((val >> 24) & 0xff, dev->iobase + KE_SIGN_REG(chan));
+ outb((val >> 16) & 0xff, dev->iobase + KE_MSB_REG(chan));
+ outb((val >> 8) & 0xff, dev->iobase + KE_MID_REG(chan));
+ outb((val >> 0) & 0xff, dev->iobase + KE_LSB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int ke_counter_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Order matters */
+ inb(dev->iobase + KE_LATCH_REG(chan));
+
+ val = inb(dev->iobase + KE_LSB_REG(chan));
+ val |= (inb(dev->iobase + KE_MID_REG(chan)) << 8);
+ val |= (inb(dev->iobase + KE_MSB_REG(chan)) << 16);
+ val |= (inb(dev->iobase + KE_SIGN_REG(chan)) << 24);
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static void ke_counter_reset(struct comedi_device *dev)
+{
+ unsigned int chan;
+
+ for (chan = 0; chan < 3; chan++)
+ outb(0, dev->iobase + KE_RESET_REG(chan));
+}
+
+static int ke_counter_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned char src;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ switch (data[1]) {
+ case KE_CLK_20MHZ: /* default */
+ src = KE_OSC_SEL_20MHZ;
+ break;
+ case KE_CLK_4MHZ: /* option */
+ src = KE_OSC_SEL_4MHZ;
+ break;
+ case KE_CLK_EXT: /* Pin 21 on D-sub */
+ src = KE_OSC_SEL_EXT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ outb(src, dev->iobase + KE_OSC_SEL_REG);
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ src = inb(dev->iobase + KE_OSC_SEL_REG);
+ switch (src) {
+ case KE_OSC_SEL_20MHZ:
+ data[1] = KE_CLK_20MHZ;
+ data[2] = 50; /* 50ns */
+ break;
+ case KE_OSC_SEL_4MHZ:
+ data[1] = KE_CLK_4MHZ;
+ data[2] = 250; /* 250ns */
+ break;
+ case KE_OSC_SEL_EXT:
+ data[1] = KE_CLK_EXT;
+ data[2] = 0; /* Unknown */
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case INSN_CONFIG_RESET:
+ ke_counter_reset(dev);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int ke_counter_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + KE_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int ke_counter_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 3;
+ s->maxdata = 0x01ffffff;
+ s->range_table = &range_unknown;
+ s->insn_read = ke_counter_insn_read;
+ s->insn_write = ke_counter_insn_write;
+ s->insn_config = ke_counter_insn_config;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 3;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ke_counter_do_insn_bits;
+
+ outb(KE_OSC_SEL_20MHZ, dev->iobase + KE_OSC_SEL_REG);
+
+ ke_counter_reset(dev);
+
+ return 0;
+}
+
+static struct comedi_driver ke_counter_driver = {
+ .driver_name = "ke_counter",
+ .module = THIS_MODULE,
+ .auto_attach = ke_counter_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int ke_counter_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ke_counter_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id ke_counter_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_KOLTER, 0x0014) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ke_counter_pci_table);
+
+static struct pci_driver ke_counter_pci_driver = {
+ .name = "ke_counter",
+ .id_table = ke_counter_pci_table,
+ .probe = ke_counter_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ke_counter_driver, ke_counter_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Kolter Electronic Counter Card");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/me4000.c b/drivers/staging/comedi/drivers/me4000.c
new file mode 100644
index 000000000..0423cf875
--- /dev/null
+++ b/drivers/staging/comedi/drivers/me4000.c
@@ -0,0 +1,1443 @@
+/*
+ comedi/drivers/me4000.c
+ Source code for the Meilhaus ME-4000 board family.
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+/*
+Driver: me4000
+Description: Meilhaus ME-4000 series boards
+Devices: [Meilhaus] ME-4650 (me4000), ME-4670i, ME-4680, ME-4680i, ME-4680is
+Author: gg (Guenter Gebhardt <g.gebhardt@meilhaus.com>)
+Updated: Mon, 18 Mar 2002 15:34:01 -0800
+Status: broken (no support for loading firmware)
+
+Supports:
+
+ - Analog Input
+ - Analog Output
+ - Digital I/O
+ - Counter
+
+Configuration Options: not applicable, uses PCI auto config
+
+The firmware required by these boards is available in the
+comedi_nonfree_firmware tarball available from
+http://www.comedi.org. However, the driver's support for
+loading the firmware through comedi_config is currently
+broken.
+
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "plx9052.h"
+
+#define ME4000_FIRMWARE "/*(DEBLOBBED)*/"
+
+/*
+ * ME4000 Register map and bit defines
+ */
+#define ME4000_AO_CHAN(x) ((x) * 0x18)
+
+#define ME4000_AO_CTRL_REG(x) (0x00 + ME4000_AO_CHAN(x))
+#define ME4000_AO_CTRL_BIT_MODE_0 (1 << 0)
+#define ME4000_AO_CTRL_BIT_MODE_1 (1 << 1)
+#define ME4000_AO_CTRL_MASK_MODE (3 << 0)
+#define ME4000_AO_CTRL_BIT_STOP (1 << 2)
+#define ME4000_AO_CTRL_BIT_ENABLE_FIFO (1 << 3)
+#define ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG (1 << 4)
+#define ME4000_AO_CTRL_BIT_EX_TRIG_EDGE (1 << 5)
+#define ME4000_AO_CTRL_BIT_IMMEDIATE_STOP (1 << 7)
+#define ME4000_AO_CTRL_BIT_ENABLE_DO (1 << 8)
+#define ME4000_AO_CTRL_BIT_ENABLE_IRQ (1 << 9)
+#define ME4000_AO_CTRL_BIT_RESET_IRQ (1 << 10)
+#define ME4000_AO_STATUS_REG(x) (0x04 + ME4000_AO_CHAN(x))
+#define ME4000_AO_STATUS_BIT_FSM (1 << 0)
+#define ME4000_AO_STATUS_BIT_FF (1 << 1)
+#define ME4000_AO_STATUS_BIT_HF (1 << 2)
+#define ME4000_AO_STATUS_BIT_EF (1 << 3)
+#define ME4000_AO_FIFO_REG(x) (0x08 + ME4000_AO_CHAN(x))
+#define ME4000_AO_SINGLE_REG(x) (0x0c + ME4000_AO_CHAN(x))
+#define ME4000_AO_TIMER_REG(x) (0x10 + ME4000_AO_CHAN(x))
+#define ME4000_AI_CTRL_REG 0x74
+#define ME4000_AI_STATUS_REG 0x74
+#define ME4000_AI_CTRL_BIT_MODE_0 (1 << 0)
+#define ME4000_AI_CTRL_BIT_MODE_1 (1 << 1)
+#define ME4000_AI_CTRL_BIT_MODE_2 (1 << 2)
+#define ME4000_AI_CTRL_BIT_SAMPLE_HOLD (1 << 3)
+#define ME4000_AI_CTRL_BIT_IMMEDIATE_STOP (1 << 4)
+#define ME4000_AI_CTRL_BIT_STOP (1 << 5)
+#define ME4000_AI_CTRL_BIT_CHANNEL_FIFO (1 << 6)
+#define ME4000_AI_CTRL_BIT_DATA_FIFO (1 << 7)
+#define ME4000_AI_CTRL_BIT_FULLSCALE (1 << 8)
+#define ME4000_AI_CTRL_BIT_OFFSET (1 << 9)
+#define ME4000_AI_CTRL_BIT_EX_TRIG_ANALOG (1 << 10)
+#define ME4000_AI_CTRL_BIT_EX_TRIG (1 << 11)
+#define ME4000_AI_CTRL_BIT_EX_TRIG_FALLING (1 << 12)
+#define ME4000_AI_CTRL_BIT_EX_IRQ (1 << 13)
+#define ME4000_AI_CTRL_BIT_EX_IRQ_RESET (1 << 14)
+#define ME4000_AI_CTRL_BIT_LE_IRQ (1 << 15)
+#define ME4000_AI_CTRL_BIT_LE_IRQ_RESET (1 << 16)
+#define ME4000_AI_CTRL_BIT_HF_IRQ (1 << 17)
+#define ME4000_AI_CTRL_BIT_HF_IRQ_RESET (1 << 18)
+#define ME4000_AI_CTRL_BIT_SC_IRQ (1 << 19)
+#define ME4000_AI_CTRL_BIT_SC_IRQ_RESET (1 << 20)
+#define ME4000_AI_CTRL_BIT_SC_RELOAD (1 << 21)
+#define ME4000_AI_STATUS_BIT_EF_CHANNEL (1 << 22)
+#define ME4000_AI_STATUS_BIT_HF_CHANNEL (1 << 23)
+#define ME4000_AI_STATUS_BIT_FF_CHANNEL (1 << 24)
+#define ME4000_AI_STATUS_BIT_EF_DATA (1 << 25)
+#define ME4000_AI_STATUS_BIT_HF_DATA (1 << 26)
+#define ME4000_AI_STATUS_BIT_FF_DATA (1 << 27)
+#define ME4000_AI_STATUS_BIT_LE (1 << 28)
+#define ME4000_AI_STATUS_BIT_FSM (1 << 29)
+#define ME4000_AI_CTRL_BIT_EX_TRIG_BOTH (1 << 31)
+#define ME4000_AI_CHANNEL_LIST_REG 0x78
+#define ME4000_AI_LIST_INPUT_SINGLE_ENDED (0 << 5)
+#define ME4000_AI_LIST_INPUT_DIFFERENTIAL (1 << 5)
+#define ME4000_AI_LIST_RANGE_BIPOLAR_10 (0 << 6)
+#define ME4000_AI_LIST_RANGE_BIPOLAR_2_5 (1 << 6)
+#define ME4000_AI_LIST_RANGE_UNIPOLAR_10 (2 << 6)
+#define ME4000_AI_LIST_RANGE_UNIPOLAR_2_5 (3 << 6)
+#define ME4000_AI_LIST_LAST_ENTRY (1 << 8)
+#define ME4000_AI_DATA_REG 0x7c
+#define ME4000_AI_CHAN_TIMER_REG 0x80
+#define ME4000_AI_CHAN_PRE_TIMER_REG 0x84
+#define ME4000_AI_SCAN_TIMER_LOW_REG 0x88
+#define ME4000_AI_SCAN_TIMER_HIGH_REG 0x8c
+#define ME4000_AI_SCAN_PRE_TIMER_LOW_REG 0x90
+#define ME4000_AI_SCAN_PRE_TIMER_HIGH_REG 0x94
+#define ME4000_AI_START_REG 0x98
+#define ME4000_IRQ_STATUS_REG 0x9c
+#define ME4000_IRQ_STATUS_BIT_EX (1 << 0)
+#define ME4000_IRQ_STATUS_BIT_LE (1 << 1)
+#define ME4000_IRQ_STATUS_BIT_AI_HF (1 << 2)
+#define ME4000_IRQ_STATUS_BIT_AO_0_HF (1 << 3)
+#define ME4000_IRQ_STATUS_BIT_AO_1_HF (1 << 4)
+#define ME4000_IRQ_STATUS_BIT_AO_2_HF (1 << 5)
+#define ME4000_IRQ_STATUS_BIT_AO_3_HF (1 << 6)
+#define ME4000_IRQ_STATUS_BIT_SC (1 << 7)
+#define ME4000_DIO_PORT_0_REG 0xa0
+#define ME4000_DIO_PORT_1_REG 0xa4
+#define ME4000_DIO_PORT_2_REG 0xa8
+#define ME4000_DIO_PORT_3_REG 0xac
+#define ME4000_DIO_DIR_REG 0xb0
+#define ME4000_AO_LOADSETREG_XX 0xb4
+#define ME4000_DIO_CTRL_REG 0xb8
+#define ME4000_DIO_CTRL_BIT_MODE_0 (1 << 0)
+#define ME4000_DIO_CTRL_BIT_MODE_1 (1 << 1)
+#define ME4000_DIO_CTRL_BIT_MODE_2 (1 << 2)
+#define ME4000_DIO_CTRL_BIT_MODE_3 (1 << 3)
+#define ME4000_DIO_CTRL_BIT_MODE_4 (1 << 4)
+#define ME4000_DIO_CTRL_BIT_MODE_5 (1 << 5)
+#define ME4000_DIO_CTRL_BIT_MODE_6 (1 << 6)
+#define ME4000_DIO_CTRL_BIT_MODE_7 (1 << 7)
+#define ME4000_DIO_CTRL_BIT_FUNCTION_0 (1 << 8)
+#define ME4000_DIO_CTRL_BIT_FUNCTION_1 (1 << 9)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_0 (1 << 10)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_1 (1 << 11)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_2 (1 << 12)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_3 (1 << 13)
+#define ME4000_AO_DEMUX_ADJUST_REG 0xbc
+#define ME4000_AO_DEMUX_ADJUST_VALUE 0x4c
+#define ME4000_AI_SAMPLE_COUNTER_REG 0xc0
+
+#define ME4000_AI_FIFO_COUNT 2048
+
+#define ME4000_AI_MIN_TICKS 66
+#define ME4000_AI_MIN_SAMPLE_TIME 2000
+
+#define ME4000_AI_CHANNEL_LIST_COUNT 1024
+
+struct me4000_info {
+ unsigned long plx_regbase;
+};
+
+enum me4000_boardid {
+ BOARD_ME4650,
+ BOARD_ME4660,
+ BOARD_ME4660I,
+ BOARD_ME4660S,
+ BOARD_ME4660IS,
+ BOARD_ME4670,
+ BOARD_ME4670I,
+ BOARD_ME4670S,
+ BOARD_ME4670IS,
+ BOARD_ME4680,
+ BOARD_ME4680I,
+ BOARD_ME4680S,
+ BOARD_ME4680IS,
+};
+
+struct me4000_board {
+ const char *name;
+ int ao_nchan;
+ int ao_fifo;
+ int ai_nchan;
+ int ai_diff_nchan;
+ int ai_sh_nchan;
+ int ex_trig_analog;
+ int dio_nchan;
+ int has_counter;
+};
+
+static const struct me4000_board me4000_boards[] = {
+ [BOARD_ME4650] = {
+ .name = "ME-4650",
+ .ai_nchan = 16,
+ .dio_nchan = 32,
+ },
+ [BOARD_ME4660] = {
+ .name = "ME-4660",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4660I] = {
+ .name = "ME-4660i",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4660S] = {
+ .name = "ME-4660s",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4660IS] = {
+ .name = "ME-4660is",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670] = {
+ .name = "ME-4670",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670I] = {
+ .name = "ME-4670i",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670S] = {
+ .name = "ME-4670s",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670IS] = {
+ .name = "ME-4670is",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680] = {
+ .name = "ME-4680",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680I] = {
+ .name = "ME-4680i",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680S] = {
+ .name = "ME-4680s",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680IS] = {
+ .name = "ME-4680is",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+};
+
+static const struct comedi_lrange me4000_ai_range = {
+ 4, {
+ UNI_RANGE(2.5),
+ UNI_RANGE(10),
+ BIP_RANGE(2.5),
+ BIP_RANGE(10)
+ }
+};
+
+static int me4000_xilinx_download(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct me4000_info *info = dev->private;
+ unsigned long xilinx_iobase = pci_resource_start(pcidev, 5);
+ unsigned int file_length;
+ unsigned int val;
+ unsigned int i;
+
+ if (!xilinx_iobase)
+ return -ENODEV;
+
+ /*
+ * Set PLX local interrupt 2 polarity to high.
+ * Interrupt is thrown by init pin of xilinx.
+ */
+ outl(PLX9052_INTCSR_LI2POL, info->plx_regbase + PLX9052_INTCSR);
+
+ /* Set /CS and /WRITE of the Xilinx */
+ val = inl(info->plx_regbase + PLX9052_CNTRL);
+ val |= PLX9052_CNTRL_UIO2_DATA;
+ outl(val, info->plx_regbase + PLX9052_CNTRL);
+
+ /* Init Xilinx with CS1 */
+ inb(xilinx_iobase + 0xC8);
+
+ /* Wait until /INIT pin is set */
+ udelay(20);
+ val = inl(info->plx_regbase + PLX9052_INTCSR);
+ if (!(val & PLX9052_INTCSR_LI2STAT)) {
+ dev_err(dev->class_dev, "Can't init Xilinx\n");
+ return -EIO;
+ }
+
+ /* Reset /CS and /WRITE of the Xilinx */
+ val = inl(info->plx_regbase + PLX9052_CNTRL);
+ val &= ~PLX9052_CNTRL_UIO2_DATA;
+ outl(val, info->plx_regbase + PLX9052_CNTRL);
+
+ /* Download Xilinx firmware */
+ file_length = (((unsigned int)data[0] & 0xff) << 24) +
+ (((unsigned int)data[1] & 0xff) << 16) +
+ (((unsigned int)data[2] & 0xff) << 8) +
+ ((unsigned int)data[3] & 0xff);
+ udelay(10);
+
+ for (i = 0; i < file_length; i++) {
+ outb(data[16 + i], xilinx_iobase);
+ udelay(10);
+
+ /* Check if BUSY flag is low */
+ val = inl(info->plx_regbase + PLX9052_CNTRL);
+ if (val & PLX9052_CNTRL_UIO1_DATA) {
+ dev_err(dev->class_dev,
+ "Xilinx is still busy (i = %d)\n", i);
+ return -EIO;
+ }
+ }
+
+ /* If done flag is high download was successful */
+ val = inl(info->plx_regbase + PLX9052_CNTRL);
+ if (!(val & PLX9052_CNTRL_UIO0_DATA)) {
+ dev_err(dev->class_dev, "DONE flag is not set\n");
+ dev_err(dev->class_dev, "Download not successful\n");
+ return -EIO;
+ }
+
+ /* Set /CS and /WRITE */
+ val = inl(info->plx_regbase + PLX9052_CNTRL);
+ val |= PLX9052_CNTRL_UIO2_DATA;
+ outl(val, info->plx_regbase + PLX9052_CNTRL);
+
+ return 0;
+}
+
+static void me4000_reset(struct comedi_device *dev)
+{
+ struct me4000_info *info = dev->private;
+ unsigned int val;
+ int chan;
+
+ /* Make a hardware reset */
+ val = inl(info->plx_regbase + PLX9052_CNTRL);
+ val |= PLX9052_CNTRL_PCI_RESET;
+ outl(val, info->plx_regbase + PLX9052_CNTRL);
+ val &= ~PLX9052_CNTRL_PCI_RESET;
+ outl(val, info->plx_regbase + PLX9052_CNTRL);
+
+ /* 0x8000 to the DACs means an output voltage of 0V */
+ for (chan = 0; chan < 4; chan++)
+ outl(0x8000, dev->iobase + ME4000_AO_SINGLE_REG(chan));
+
+ /* Set both stop bits in the analog input control register */
+ outl(ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP,
+ dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Set both stop bits in the analog output control register */
+ val = ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP;
+ for (chan = 0; chan < 4; chan++)
+ outl(val, dev->iobase + ME4000_AO_CTRL_REG(chan));
+
+ /* Enable interrupts on the PLX */
+ outl(PLX9052_INTCSR_LI1ENAB |
+ PLX9052_INTCSR_LI1POL |
+ PLX9052_INTCSR_PCIENAB, info->plx_regbase + PLX9052_INTCSR);
+
+ /* Set the adustment register for AO demux */
+ outl(ME4000_AO_DEMUX_ADJUST_VALUE,
+ dev->iobase + ME4000_AO_DEMUX_ADJUST_REG);
+
+ /*
+ * Set digital I/O direction for port 0
+ * to output on isolated versions
+ */
+ if (!(inl(dev->iobase + ME4000_DIO_DIR_REG) & 0x1))
+ outl(0x1, dev->iobase + ME4000_DIO_CTRL_REG);
+}
+
+/*=============================================================================
+ Analog input section
+ ===========================================================================*/
+
+static int me4000_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *subdevice,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct me4000_board *thisboard = dev->board_ptr;
+ int chan = CR_CHAN(insn->chanspec);
+ int rang = CR_RANGE(insn->chanspec);
+ int aref = CR_AREF(insn->chanspec);
+
+ unsigned int entry = 0;
+ unsigned int tmp;
+ unsigned int lval;
+
+ if (insn->n == 0) {
+ return 0;
+ } else if (insn->n > 1) {
+ dev_err(dev->class_dev, "Invalid instruction length %d\n",
+ insn->n);
+ return -EINVAL;
+ }
+
+ switch (rang) {
+ case 0:
+ entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5;
+ break;
+ case 1:
+ entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10;
+ break;
+ case 2:
+ entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5;
+ break;
+ case 3:
+ entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10;
+ break;
+ default:
+ dev_err(dev->class_dev, "Invalid range specified\n");
+ return -EINVAL;
+ }
+
+ switch (aref) {
+ case AREF_GROUND:
+ case AREF_COMMON:
+ if (chan >= thisboard->ai_nchan) {
+ dev_err(dev->class_dev,
+ "Analog input is not available\n");
+ return -EINVAL;
+ }
+ entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED | chan;
+ break;
+
+ case AREF_DIFF:
+ if (rang == 0 || rang == 1) {
+ dev_err(dev->class_dev,
+ "Range must be bipolar when aref = diff\n");
+ return -EINVAL;
+ }
+
+ if (chan >= thisboard->ai_diff_nchan) {
+ dev_err(dev->class_dev,
+ "Analog input is not available\n");
+ return -EINVAL;
+ }
+ entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL | chan;
+ break;
+ default:
+ dev_err(dev->class_dev, "Invalid aref specified\n");
+ return -EINVAL;
+ }
+
+ entry |= ME4000_AI_LIST_LAST_ENTRY;
+
+ /* Clear channel list, data fifo and both stop bits */
+ tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
+ tmp &= ~(ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+ ME4000_AI_CTRL_BIT_DATA_FIFO |
+ ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Set the acquisition mode to single */
+ tmp &= ~(ME4000_AI_CTRL_BIT_MODE_0 | ME4000_AI_CTRL_BIT_MODE_1 |
+ ME4000_AI_CTRL_BIT_MODE_2);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Enable channel list and data fifo */
+ tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Generate channel list entry */
+ outl(entry, dev->iobase + ME4000_AI_CHANNEL_LIST_REG);
+
+ /* Set the timer to maximum sample rate */
+ outl(ME4000_AI_MIN_TICKS, dev->iobase + ME4000_AI_CHAN_TIMER_REG);
+ outl(ME4000_AI_MIN_TICKS, dev->iobase + ME4000_AI_CHAN_PRE_TIMER_REG);
+
+ /* Start conversion by dummy read */
+ inl(dev->iobase + ME4000_AI_START_REG);
+
+ /* Wait until ready */
+ udelay(10);
+ if (!(inl(dev->iobase + ME4000_AI_STATUS_REG) &
+ ME4000_AI_STATUS_BIT_EF_DATA)) {
+ dev_err(dev->class_dev, "Value not available after wait\n");
+ return -EIO;
+ }
+
+ /* Read value from data fifo */
+ lval = inl(dev->iobase + ME4000_AI_DATA_REG) & 0xFFFF;
+ data[0] = lval ^ 0x8000;
+
+ return 1;
+}
+
+static int me4000_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int tmp;
+
+ /* Stop any running conversion */
+ tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
+ tmp &= ~(ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Clear the control register */
+ outl(0x0, dev->iobase + ME4000_AI_CTRL_REG);
+
+ return 0;
+}
+
+static int me4000_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct me4000_board *board = dev->board_ptr;
+ unsigned int max_diff_chan = board->ai_diff_nchan;
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "Mode is not equal for all entries\n");
+ return -EINVAL;
+ }
+
+ if (aref == AREF_DIFF) {
+ if (chan >= max_diff_chan) {
+ dev_dbg(dev->class_dev,
+ "Channel number to high\n");
+ return -EINVAL;
+ }
+
+ if (!comedi_range_is_bipolar(s, range)) {
+ dev_dbg(dev->class_dev,
+ "Bipolar is not selected in differential mode\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ai_round_cmd_args(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd,
+ unsigned int *init_ticks,
+ unsigned int *scan_ticks, unsigned int *chan_ticks)
+{
+ int rest;
+
+ *init_ticks = 0;
+ *scan_ticks = 0;
+ *chan_ticks = 0;
+
+ if (cmd->start_arg) {
+ *init_ticks = (cmd->start_arg * 33) / 1000;
+ rest = (cmd->start_arg * 33) % 1000;
+
+ if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) {
+ if (rest > 33)
+ (*init_ticks)++;
+ } else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) {
+ if (rest)
+ (*init_ticks)++;
+ }
+ }
+
+ if (cmd->scan_begin_arg) {
+ *scan_ticks = (cmd->scan_begin_arg * 33) / 1000;
+ rest = (cmd->scan_begin_arg * 33) % 1000;
+
+ if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) {
+ if (rest > 33)
+ (*scan_ticks)++;
+ } else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) {
+ if (rest)
+ (*scan_ticks)++;
+ }
+ }
+
+ if (cmd->convert_arg) {
+ *chan_ticks = (cmd->convert_arg * 33) / 1000;
+ rest = (cmd->convert_arg * 33) % 1000;
+
+ if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) {
+ if (rest > 33)
+ (*chan_ticks)++;
+ } else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) {
+ if (rest)
+ (*chan_ticks)++;
+ }
+ }
+
+ return 0;
+}
+
+static void ai_write_timer(struct comedi_device *dev,
+ unsigned int init_ticks,
+ unsigned int scan_ticks, unsigned int chan_ticks)
+{
+ outl(init_ticks - 1, dev->iobase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG);
+ outl(0x0, dev->iobase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG);
+
+ if (scan_ticks) {
+ outl(scan_ticks - 1, dev->iobase + ME4000_AI_SCAN_TIMER_LOW_REG);
+ outl(0x0, dev->iobase + ME4000_AI_SCAN_TIMER_HIGH_REG);
+ }
+
+ outl(chan_ticks - 1, dev->iobase + ME4000_AI_CHAN_PRE_TIMER_REG);
+ outl(chan_ticks - 1, dev->iobase + ME4000_AI_CHAN_TIMER_REG);
+}
+
+static int ai_write_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ unsigned int entry;
+ unsigned int chan;
+ unsigned int rang;
+ unsigned int aref;
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ chan = CR_CHAN(cmd->chanlist[i]);
+ rang = CR_RANGE(cmd->chanlist[i]);
+ aref = CR_AREF(cmd->chanlist[i]);
+
+ entry = chan;
+
+ if (rang == 0)
+ entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5;
+ else if (rang == 1)
+ entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10;
+ else if (rang == 2)
+ entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5;
+ else
+ entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10;
+
+ if (aref == AREF_DIFF)
+ entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL;
+ else
+ entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED;
+
+ outl(entry, dev->iobase + ME4000_AI_CHANNEL_LIST_REG);
+ }
+
+ return 0;
+}
+
+static int ai_prepare(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd,
+ unsigned int init_ticks,
+ unsigned int scan_ticks, unsigned int chan_ticks)
+{
+ unsigned int tmp = 0;
+
+ /* Write timer arguments */
+ ai_write_timer(dev, init_ticks, scan_ticks, chan_ticks);
+
+ /* Reset control register */
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Start sources */
+ if ((cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src == TRIG_TIMER) ||
+ (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_TIMER)) {
+ tmp = ME4000_AI_CTRL_BIT_MODE_1 |
+ ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+ ME4000_AI_CTRL_BIT_DATA_FIFO;
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_EXT &&
+ cmd->convert_src == TRIG_TIMER) {
+ tmp = ME4000_AI_CTRL_BIT_MODE_2 |
+ ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+ ME4000_AI_CTRL_BIT_DATA_FIFO;
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_EXT &&
+ cmd->convert_src == TRIG_EXT) {
+ tmp = ME4000_AI_CTRL_BIT_MODE_0 |
+ ME4000_AI_CTRL_BIT_MODE_1 |
+ ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+ ME4000_AI_CTRL_BIT_DATA_FIFO;
+ } else {
+ tmp = ME4000_AI_CTRL_BIT_MODE_0 |
+ ME4000_AI_CTRL_BIT_CHANNEL_FIFO |
+ ME4000_AI_CTRL_BIT_DATA_FIFO;
+ }
+
+ /* Stop triggers */
+ if (cmd->stop_src == TRIG_COUNT) {
+ outl(cmd->chanlist_len * cmd->stop_arg,
+ dev->iobase + ME4000_AI_SAMPLE_COUNTER_REG);
+ tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ;
+ } else if (cmd->stop_src == TRIG_NONE &&
+ cmd->scan_end_src == TRIG_COUNT) {
+ outl(cmd->scan_end_arg,
+ dev->iobase + ME4000_AI_SAMPLE_COUNTER_REG);
+ tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ;
+ } else {
+ tmp |= ME4000_AI_CTRL_BIT_HF_IRQ;
+ }
+
+ /* Write the setup to the control register */
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Write the channel list */
+ ai_write_chanlist(dev, s, cmd);
+
+ return 0;
+}
+
+static int me4000_ai_do_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ int err;
+ unsigned int init_ticks = 0;
+ unsigned int scan_ticks = 0;
+ unsigned int chan_ticks = 0;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ /* Reset the analog input */
+ err = me4000_ai_cancel(dev, s);
+ if (err)
+ return err;
+
+ /* Round the timer arguments */
+ err = ai_round_cmd_args(dev,
+ s, cmd, &init_ticks, &scan_ticks, &chan_ticks);
+ if (err)
+ return err;
+
+ /* Prepare the AI for acquisition */
+ err = ai_prepare(dev, s, cmd, init_ticks, scan_ticks, chan_ticks);
+ if (err)
+ return err;
+
+ /* Start acquistion by dummy read */
+ inl(dev->iobase + ME4000_AI_START_REG);
+
+ return 0;
+}
+
+static int me4000_ai_do_cmd_test(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int init_ticks;
+ unsigned int chan_ticks;
+ unsigned int scan_ticks;
+ int err = 0;
+
+ /* Round the timer arguments */
+ ai_round_cmd_args(dev, s, cmd, &init_ticks, &scan_ticks, &chan_ticks);
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src,
+ TRIG_NONE | TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE | TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_end_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->start_src == TRIG_NOW &&
+ cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src == TRIG_TIMER) {
+ } else if (cmd->start_src == TRIG_NOW &&
+ cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_TIMER) {
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src == TRIG_TIMER) {
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_TIMER) {
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_EXT &&
+ cmd->convert_src == TRIG_TIMER) {
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_EXT &&
+ cmd->convert_src == TRIG_EXT) {
+ } else {
+ err |= -EINVAL;
+ }
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->chanlist_len < 1) {
+ cmd->chanlist_len = 1;
+ err |= -EINVAL;
+ }
+ if (init_ticks < 66) {
+ cmd->start_arg = 2000;
+ err |= -EINVAL;
+ }
+ if (scan_ticks && scan_ticks < 67) {
+ cmd->scan_begin_arg = 2031;
+ err |= -EINVAL;
+ }
+ if (chan_ticks < 66) {
+ cmd->convert_arg = 2000;
+ err |= -EINVAL;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /*
+ * Stage 4. Check for argument conflicts.
+ */
+ if (cmd->start_src == TRIG_NOW &&
+ cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* Check timer arguments */
+ if (init_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ cmd->convert_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
+ dev_err(dev->class_dev, "Invalid scan end arg\n");
+
+ /* At least one tick more */
+ cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;
+ err++;
+ }
+ } else if (cmd->start_src == TRIG_NOW &&
+ cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* Check timer arguments */
+ if (init_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ cmd->convert_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* Check timer arguments */
+ if (init_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ cmd->convert_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
+ dev_err(dev->class_dev, "Invalid scan end arg\n");
+
+ /* At least one tick more */
+ cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;
+ err++;
+ }
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* Check timer arguments */
+ if (init_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ cmd->convert_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_EXT &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* Check timer arguments */
+ if (init_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ cmd->convert_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ } else if (cmd->start_src == TRIG_EXT &&
+ cmd->scan_begin_src == TRIG_EXT &&
+ cmd->convert_src == TRIG_EXT) {
+ /* Check timer arguments */
+ if (init_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ }
+ if (cmd->scan_end_src == TRIG_COUNT) {
+ if (cmd->scan_end_arg == 0) {
+ dev_err(dev->class_dev, "Invalid scan end arg\n");
+ cmd->scan_end_arg = 1;
+ err++;
+ }
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= me4000_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
+{
+ unsigned int tmp;
+ struct comedi_device *dev = dev_id;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int i;
+ int c = 0;
+ unsigned int lval;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) &
+ ME4000_IRQ_STATUS_BIT_AI_HF) {
+ /* Read status register to find out what happened */
+ tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
+
+ if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) &&
+ !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) &&
+ (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
+ c = ME4000_AI_FIFO_COUNT;
+
+ /*
+ * FIFO overflow, so stop conversion
+ * and disable all interrupts
+ */
+ tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+ tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
+ ME4000_AI_CTRL_BIT_SC_IRQ);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ s->async->events |= COMEDI_CB_ERROR;
+
+ dev_err(dev->class_dev, "FIFO overflow\n");
+ } else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA)
+ && !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
+ && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
+ c = ME4000_AI_FIFO_COUNT / 2;
+ } else {
+ dev_err(dev->class_dev,
+ "Can't determine state of fifo\n");
+ c = 0;
+
+ /*
+ * Undefined state, so stop conversion
+ * and disable all interrupts
+ */
+ tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+ tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
+ ME4000_AI_CTRL_BIT_SC_IRQ);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ s->async->events |= COMEDI_CB_ERROR;
+
+ dev_err(dev->class_dev, "Undefined FIFO state\n");
+ }
+
+ for (i = 0; i < c; i++) {
+ /* Read value from data fifo */
+ lval = inl(dev->iobase + ME4000_AI_DATA_REG) & 0xFFFF;
+ lval ^= 0x8000;
+
+ if (!comedi_buf_write_samples(s, &lval, 1)) {
+ /*
+ * Buffer overflow, so stop conversion
+ * and disable all interrupts
+ */
+ tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+ tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
+ ME4000_AI_CTRL_BIT_SC_IRQ);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ break;
+ }
+ }
+
+ /* Work is done, so reset the interrupt */
+ tmp |= ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ tmp &= ~ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ }
+
+ if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) &
+ ME4000_IRQ_STATUS_BIT_SC) {
+ s->async->events |= COMEDI_CB_EOA;
+
+ /*
+ * Acquisition is complete, so stop
+ * conversion and disable all interrupts
+ */
+ tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
+ tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+ tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Poll data until fifo empty */
+ while (inl(dev->iobase + ME4000_AI_CTRL_REG) &
+ ME4000_AI_STATUS_BIT_EF_DATA) {
+ /* Read value from data fifo */
+ lval = inl(dev->iobase + ME4000_AI_DATA_REG) & 0xFFFF;
+ lval ^= 0x8000;
+
+ if (!comedi_buf_write_samples(s, &lval, 1))
+ break;
+ }
+
+ /* Work is done, so reset the interrupt */
+ tmp |= ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ tmp &= ~ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ }
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int me4000_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int chan = CR_CHAN(insn->chanspec);
+ unsigned int tmp;
+
+ /* Stop any running conversion */
+ tmp = inl(dev->iobase + ME4000_AO_CTRL_REG(chan));
+ tmp |= ME4000_AO_CTRL_BIT_IMMEDIATE_STOP;
+ outl(tmp, dev->iobase + ME4000_AO_CTRL_REG(chan));
+
+ /* Clear control register and set to single mode */
+ outl(0x0, dev->iobase + ME4000_AO_CTRL_REG(chan));
+
+ /* Write data value */
+ outl(data[0], dev->iobase + ME4000_AO_SINGLE_REG(chan));
+
+ /* Store in the mirror */
+ s->readback[chan] = data[0];
+
+ return 1;
+}
+
+static int me4000_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outl((s->state >> 0) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_0_REG);
+ outl((s->state >> 8) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_1_REG);
+ outl((s->state >> 16) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_2_REG);
+ outl((s->state >> 24) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_3_REG);
+ }
+
+ data[1] = ((inl(dev->iobase + ME4000_DIO_PORT_0_REG) & 0xFF) << 0) |
+ ((inl(dev->iobase + ME4000_DIO_PORT_1_REG) & 0xFF) << 8) |
+ ((inl(dev->iobase + ME4000_DIO_PORT_2_REG) & 0xFF) << 16) |
+ ((inl(dev->iobase + ME4000_DIO_PORT_3_REG) & 0xFF) << 24);
+
+ return insn->n;
+}
+
+static int me4000_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ unsigned int tmp;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ tmp = inl(dev->iobase + ME4000_DIO_CTRL_REG);
+ tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | ME4000_DIO_CTRL_BIT_MODE_1 |
+ ME4000_DIO_CTRL_BIT_MODE_2 | ME4000_DIO_CTRL_BIT_MODE_3 |
+ ME4000_DIO_CTRL_BIT_MODE_4 | ME4000_DIO_CTRL_BIT_MODE_5 |
+ ME4000_DIO_CTRL_BIT_MODE_6 | ME4000_DIO_CTRL_BIT_MODE_7);
+ if (s->io_bits & 0x000000ff)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_0;
+ if (s->io_bits & 0x0000ff00)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_2;
+ if (s->io_bits & 0x00ff0000)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_4;
+ if (s->io_bits & 0xff000000)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_6;
+
+ /*
+ * Check for optoisolated ME-4000 version.
+ * If one the first port is a fixed output
+ * port and the second is a fixed input port.
+ */
+ if (inl(dev->iobase + ME4000_DIO_DIR_REG)) {
+ s->io_bits |= 0x000000ff;
+ s->io_bits &= ~0x0000ff00;
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_0;
+ tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 |
+ ME4000_DIO_CTRL_BIT_MODE_3);
+ }
+
+ outl(tmp, dev->iobase + ME4000_DIO_CTRL_REG);
+
+ return insn->n;
+}
+
+static int me4000_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct me4000_board *thisboard = NULL;
+ struct me4000_info *info;
+ struct comedi_subdevice *s;
+ int result;
+
+ if (context < ARRAY_SIZE(me4000_boards))
+ thisboard = &me4000_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ info = comedi_alloc_devpriv(dev, sizeof(*info));
+ if (!info)
+ return -ENOMEM;
+
+ result = comedi_pci_enable(dev);
+ if (result)
+ return result;
+
+ info->plx_regbase = pci_resource_start(pcidev, 1);
+ dev->iobase = pci_resource_start(pcidev, 2);
+ if (!info->plx_regbase || !dev->iobase)
+ return -ENODEV;
+
+ result = comedi_load_firmware(dev, &pcidev->dev, ME4000_FIRMWARE,
+ me4000_xilinx_download, 0);
+ if (result < 0)
+ return result;
+
+ me4000_reset(dev);
+
+ if (pcidev->irq > 0) {
+ result = request_irq(pcidev->irq, me4000_ai_isr, IRQF_SHARED,
+ dev->board_name, dev);
+ if (result == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ result = comedi_alloc_subdevices(dev, 4);
+ if (result)
+ return result;
+
+ /*=========================================================================
+ Analog input subdevice
+ ========================================================================*/
+
+ s = &dev->subdevices[0];
+
+ if (thisboard->ai_nchan) {
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags =
+ SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
+ s->n_chan = thisboard->ai_nchan;
+ s->maxdata = 0xFFFF; /* 16 bit ADC */
+ s->len_chanlist = ME4000_AI_CHANNEL_LIST_COUNT;
+ s->range_table = &me4000_ai_range;
+ s->insn_read = me4000_ai_insn_read;
+
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->cancel = me4000_ai_cancel;
+ s->do_cmdtest = me4000_ai_do_cmd_test;
+ s->do_cmd = me4000_ai_do_cmd;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /*=========================================================================
+ Analog output subdevice
+ ========================================================================*/
+
+ s = &dev->subdevices[1];
+
+ if (thisboard->ao_nchan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_COMMON | SDF_GROUND;
+ s->n_chan = thisboard->ao_nchan;
+ s->maxdata = 0xFFFF; /* 16 bit DAC */
+ s->range_table = &range_bipolar10;
+ s->insn_write = me4000_ao_insn_write;
+
+ result = comedi_alloc_subdev_readback(s);
+ if (result)
+ return result;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /*=========================================================================
+ Digital I/O subdevice
+ ========================================================================*/
+
+ s = &dev->subdevices[2];
+
+ if (thisboard->dio_nchan) {
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = thisboard->dio_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = me4000_dio_insn_bits;
+ s->insn_config = me4000_dio_insn_config;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /*
+ * Check for optoisolated ME-4000 version. If one the first
+ * port is a fixed output port and the second is a fixed input port.
+ */
+ if (!inl(dev->iobase + ME4000_DIO_DIR_REG)) {
+ s->io_bits |= 0xFF;
+ outl(ME4000_DIO_CTRL_BIT_MODE_0,
+ dev->iobase + ME4000_DIO_DIR_REG);
+ }
+
+ /* Counter subdevice (8254) */
+ s = &dev->subdevices[3];
+ if (thisboard->has_counter) {
+ unsigned long timer_base = pci_resource_start(pcidev, 3);
+
+ if (!timer_base)
+ return -ENODEV;
+
+ dev->pacer = comedi_8254_init(timer_base, 0, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ comedi_8254_subdevice_init(s, dev->pacer);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static void me4000_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ me4000_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver me4000_driver = {
+ .driver_name = "me4000",
+ .module = THIS_MODULE,
+ .auto_attach = me4000_auto_attach,
+ .detach = me4000_detach,
+};
+
+static int me4000_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &me4000_driver, id->driver_data);
+}
+
+static const struct pci_device_id me4000_pci_table[] = {
+ { PCI_VDEVICE(MEILHAUS, 0x4650), BOARD_ME4650 },
+ { PCI_VDEVICE(MEILHAUS, 0x4660), BOARD_ME4660 },
+ { PCI_VDEVICE(MEILHAUS, 0x4661), BOARD_ME4660I },
+ { PCI_VDEVICE(MEILHAUS, 0x4662), BOARD_ME4660S },
+ { PCI_VDEVICE(MEILHAUS, 0x4663), BOARD_ME4660IS },
+ { PCI_VDEVICE(MEILHAUS, 0x4670), BOARD_ME4670 },
+ { PCI_VDEVICE(MEILHAUS, 0x4671), BOARD_ME4670I },
+ { PCI_VDEVICE(MEILHAUS, 0x4672), BOARD_ME4670S },
+ { PCI_VDEVICE(MEILHAUS, 0x4673), BOARD_ME4670IS },
+ { PCI_VDEVICE(MEILHAUS, 0x4680), BOARD_ME4680 },
+ { PCI_VDEVICE(MEILHAUS, 0x4681), BOARD_ME4680I },
+ { PCI_VDEVICE(MEILHAUS, 0x4682), BOARD_ME4680S },
+ { PCI_VDEVICE(MEILHAUS, 0x4683), BOARD_ME4680IS },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, me4000_pci_table);
+
+static struct pci_driver me4000_pci_driver = {
+ .name = "me4000",
+ .id_table = me4000_pci_table,
+ .probe = me4000_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(me4000_driver, me4000_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
+/*(DEBLOBBED)*/
diff --git a/drivers/staging/comedi/drivers/me_daq.c b/drivers/staging/comedi/drivers/me_daq.c
new file mode 100644
index 000000000..87e9f46b4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/me_daq.c
@@ -0,0 +1,583 @@
+/*
+ * comedi/drivers/me_daq.c
+ * Hardware driver for Meilhaus data acquisition cards:
+ * ME-2000i, ME-2600i, ME-3000vm1
+ *
+ * Copyright (C) 2002 Michael Hillmann <hillmann@syscongroup.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: me_daq
+ * Description: Meilhaus PCI data acquisition cards
+ * Devices: [Meilhaus] ME-2600i (me-2600i), ME-2000i (me-2000i)
+ * Author: Michael Hillmann <hillmann@syscongroup.de>
+ * Status: experimental
+ *
+ * Configuration options: not applicable, uses PCI auto config
+ *
+ * Supports:
+ * Analog Input, Analog Output, Digital I/O
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include "../comedi_pci.h"
+
+#include "plx9052.h"
+
+#define ME2600_FIRMWARE "/*(DEBLOBBED)*/"
+
+#define XILINX_DOWNLOAD_RESET 0x42 /* Xilinx registers */
+
+#define ME_CONTROL_1 0x0000 /* - | W */
+#define INTERRUPT_ENABLE (1<<15)
+#define COUNTER_B_IRQ (1<<12)
+#define COUNTER_A_IRQ (1<<11)
+#define CHANLIST_READY_IRQ (1<<10)
+#define EXT_IRQ (1<<9)
+#define ADFIFO_HALFFULL_IRQ (1<<8)
+#define SCAN_COUNT_ENABLE (1<<5)
+#define SIMULTANEOUS_ENABLE (1<<4)
+#define TRIGGER_FALLING_EDGE (1<<3)
+#define CONTINUOUS_MODE (1<<2)
+#define DISABLE_ADC (0<<0)
+#define SOFTWARE_TRIGGERED_ADC (1<<0)
+#define SCAN_TRIGGERED_ADC (2<<0)
+#define EXT_TRIGGERED_ADC (3<<0)
+#define ME_ADC_START 0x0000 /* R | - */
+#define ME_CONTROL_2 0x0002 /* - | W */
+#define ENABLE_ADFIFO (1<<10)
+#define ENABLE_CHANLIST (1<<9)
+#define ENABLE_PORT_B (1<<7)
+#define ENABLE_PORT_A (1<<6)
+#define ENABLE_COUNTER_B (1<<4)
+#define ENABLE_COUNTER_A (1<<3)
+#define ENABLE_DAC (1<<1)
+#define BUFFERED_DAC (1<<0)
+#define ME_DAC_UPDATE 0x0002 /* R | - */
+#define ME_STATUS 0x0004 /* R | - */
+#define COUNTER_B_IRQ_PENDING (1<<12)
+#define COUNTER_A_IRQ_PENDING (1<<11)
+#define CHANLIST_READY_IRQ_PENDING (1<<10)
+#define EXT_IRQ_PENDING (1<<9)
+#define ADFIFO_HALFFULL_IRQ_PENDING (1<<8)
+#define ADFIFO_FULL (1<<4)
+#define ADFIFO_HALFFULL (1<<3)
+#define ADFIFO_EMPTY (1<<2)
+#define CHANLIST_FULL (1<<1)
+#define FST_ACTIVE (1<<0)
+#define ME_RESET_INTERRUPT 0x0004 /* - | W */
+#define ME_DIO_PORT_A 0x0006 /* R | W */
+#define ME_DIO_PORT_B 0x0008 /* R | W */
+#define ME_TIMER_DATA_0 0x000A /* - | W */
+#define ME_TIMER_DATA_1 0x000C /* - | W */
+#define ME_TIMER_DATA_2 0x000E /* - | W */
+#define ME_CHANNEL_LIST 0x0010 /* - | W */
+#define ADC_UNIPOLAR (1<<6)
+#define ADC_GAIN_0 (0<<4)
+#define ADC_GAIN_1 (1<<4)
+#define ADC_GAIN_2 (2<<4)
+#define ADC_GAIN_3 (3<<4)
+#define ME_READ_AD_FIFO 0x0010 /* R | - */
+#define ME_DAC_CONTROL 0x0012 /* - | W */
+#define DAC_UNIPOLAR_D (0<<4)
+#define DAC_BIPOLAR_D (1<<4)
+#define DAC_UNIPOLAR_C (0<<5)
+#define DAC_BIPOLAR_C (1<<5)
+#define DAC_UNIPOLAR_B (0<<6)
+#define DAC_BIPOLAR_B (1<<6)
+#define DAC_UNIPOLAR_A (0<<7)
+#define DAC_BIPOLAR_A (1<<7)
+#define DAC_GAIN_0_D (0<<8)
+#define DAC_GAIN_1_D (1<<8)
+#define DAC_GAIN_0_C (0<<9)
+#define DAC_GAIN_1_C (1<<9)
+#define DAC_GAIN_0_B (0<<10)
+#define DAC_GAIN_1_B (1<<10)
+#define DAC_GAIN_0_A (0<<11)
+#define DAC_GAIN_1_A (1<<11)
+#define ME_DAC_CONTROL_UPDATE 0x0012 /* R | - */
+#define ME_DAC_DATA_A 0x0014 /* - | W */
+#define ME_DAC_DATA_B 0x0016 /* - | W */
+#define ME_DAC_DATA_C 0x0018 /* - | W */
+#define ME_DAC_DATA_D 0x001A /* - | W */
+#define ME_COUNTER_ENDDATA_A 0x001C /* - | W */
+#define ME_COUNTER_ENDDATA_B 0x001E /* - | W */
+#define ME_COUNTER_STARTDATA_A 0x0020 /* - | W */
+#define ME_COUNTER_VALUE_A 0x0020 /* R | - */
+#define ME_COUNTER_STARTDATA_B 0x0022 /* - | W */
+#define ME_COUNTER_VALUE_B 0x0022 /* R | - */
+
+static const struct comedi_lrange me_ai_range = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange me_ao_range = {
+ 3, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+enum me_boardid {
+ BOARD_ME2600,
+ BOARD_ME2000,
+};
+
+struct me_board {
+ const char *name;
+ int needs_firmware;
+ int has_ao;
+};
+
+static const struct me_board me_boards[] = {
+ [BOARD_ME2600] = {
+ .name = "me-2600i",
+ .needs_firmware = 1,
+ .has_ao = 1,
+ },
+ [BOARD_ME2000] = {
+ .name = "me-2000i",
+ },
+};
+
+struct me_private_data {
+ void __iomem *plx_regbase; /* PLX configuration base address */
+
+ unsigned short control_1; /* Mirror of CONTROL_1 register */
+ unsigned short control_2; /* Mirror of CONTROL_2 register */
+ unsigned short dac_control; /* Mirror of the DAC_CONTROL register */
+};
+
+static inline void sleep(unsigned sec)
+{
+ __set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(sec * HZ);
+}
+
+static int me_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 16)
+ mask = 0x0000ffff;
+ else
+ mask = 0xffff0000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ if (s->io_bits & 0x0000ffff)
+ devpriv->control_2 |= ENABLE_PORT_A;
+ else
+ devpriv->control_2 &= ~ENABLE_PORT_A;
+ if (s->io_bits & 0xffff0000)
+ devpriv->control_2 |= ENABLE_PORT_B;
+ else
+ devpriv->control_2 &= ~ENABLE_PORT_B;
+
+ writew(devpriv->control_2, dev->mmio + ME_CONTROL_2);
+
+ return insn->n;
+}
+
+static int me_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ void __iomem *mmio_porta = dev->mmio + ME_DIO_PORT_A;
+ void __iomem *mmio_portb = dev->mmio + ME_DIO_PORT_B;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x0000ffff)
+ writew((s->state & 0xffff), mmio_porta);
+ if (mask & 0xffff0000)
+ writew(((s->state >> 16) & 0xffff), mmio_portb);
+ }
+
+ if (s->io_bits & 0x0000ffff)
+ val = s->state & 0xffff;
+ else
+ val = readw(mmio_porta);
+
+ if (s->io_bits & 0xffff0000)
+ val |= (s->state & 0xffff0000);
+ else
+ val |= (readw(mmio_portb) << 16);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int me_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readw(dev->mmio + ME_STATUS);
+ if ((status & 0x0004) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int me_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int rang = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned short val;
+ int ret;
+
+ /* stop any running conversion */
+ dev_private->control_1 &= 0xFFFC;
+ writew(dev_private->control_1, dev->mmio + ME_CONTROL_1);
+
+ /* clear chanlist and ad fifo */
+ dev_private->control_2 &= ~(ENABLE_ADFIFO | ENABLE_CHANLIST);
+ writew(dev_private->control_2, dev->mmio + ME_CONTROL_2);
+
+ /* reset any pending interrupt */
+ writew(0x00, dev->mmio + ME_RESET_INTERRUPT);
+
+ /* enable the chanlist and ADC fifo */
+ dev_private->control_2 |= (ENABLE_ADFIFO | ENABLE_CHANLIST);
+ writew(dev_private->control_2, dev->mmio + ME_CONTROL_2);
+
+ /* write to channel list fifo */
+ val = chan & 0x0f; /* b3:b0 channel */
+ val |= (rang & 0x03) << 4; /* b5:b4 gain */
+ val |= (rang & 0x04) << 4; /* b6 polarity */
+ val |= ((aref & AREF_DIFF) ? 0x80 : 0); /* b7 differential */
+ writew(val & 0xff, dev->mmio + ME_CHANNEL_LIST);
+
+ /* set ADC mode to software trigger */
+ dev_private->control_1 |= SOFTWARE_TRIGGERED_ADC;
+ writew(dev_private->control_1, dev->mmio + ME_CONTROL_1);
+
+ /* start conversion by reading from ADC_START */
+ readw(dev->mmio + ME_ADC_START);
+
+ /* wait for ADC fifo not empty flag */
+ ret = comedi_timeout(dev, s, insn, me_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* get value from ADC fifo */
+ val = readw(dev->mmio + ME_READ_AD_FIFO);
+ val = (val ^ 0x800) & 0x0fff;
+ data[0] = val;
+
+ /* stop any running conversion */
+ dev_private->control_1 &= 0xFFFC;
+ writew(dev_private->control_1, dev->mmio + ME_CONTROL_1);
+
+ return 1;
+}
+
+static int me_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int rang = CR_RANGE(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ /* Enable all DAC */
+ dev_private->control_2 |= ENABLE_DAC;
+ writew(dev_private->control_2, dev->mmio + ME_CONTROL_2);
+
+ /* and set DAC to "buffered" mode */
+ dev_private->control_2 |= BUFFERED_DAC;
+ writew(dev_private->control_2, dev->mmio + ME_CONTROL_2);
+
+ /* Set dac-control register */
+ for (i = 0; i < insn->n; i++) {
+ /* clear bits for this channel */
+ dev_private->dac_control &= ~(0x0880 >> chan);
+ if (rang == 0)
+ dev_private->dac_control |=
+ ((DAC_BIPOLAR_A | DAC_GAIN_1_A) >> chan);
+ else if (rang == 1)
+ dev_private->dac_control |=
+ ((DAC_BIPOLAR_A | DAC_GAIN_0_A) >> chan);
+ }
+ writew(dev_private->dac_control, dev->mmio + ME_DAC_CONTROL);
+
+ /* Update dac-control register */
+ readw(dev->mmio + ME_DAC_CONTROL_UPDATE);
+
+ /* Set data register */
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ writew(val, dev->mmio + ME_DAC_DATA_A + (chan << 1));
+ }
+ s->readback[chan] = val;
+
+ /* Update dac with data registers */
+ readw(dev->mmio + ME_DAC_UPDATE);
+
+ return insn->n;
+}
+
+static int me2600_xilinx_download(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int value;
+ unsigned int file_length;
+ unsigned int i;
+
+ /* disable irq's on PLX */
+ writel(0x00, dev_private->plx_regbase + PLX9052_INTCSR);
+
+ /* First, make a dummy read to reset xilinx */
+ value = readw(dev->mmio + XILINX_DOWNLOAD_RESET);
+
+ /* Wait until reset is over */
+ sleep(1);
+
+ /* Write a dummy value to Xilinx */
+ writeb(0x00, dev->mmio + 0x0);
+ sleep(1);
+
+ /*
+ * Format of the firmware
+ * Build longs from the byte-wise coded header
+ * Byte 1-3: length of the array
+ * Byte 4-7: version
+ * Byte 8-11: date
+ * Byte 12-15: reserved
+ */
+ if (size < 16)
+ return -EINVAL;
+
+ file_length = (((unsigned int)data[0] & 0xff) << 24) +
+ (((unsigned int)data[1] & 0xff) << 16) +
+ (((unsigned int)data[2] & 0xff) << 8) +
+ ((unsigned int)data[3] & 0xff);
+
+ /*
+ * Loop for writing firmware byte by byte to xilinx
+ * Firmware data start at offset 16
+ */
+ for (i = 0; i < file_length; i++)
+ writeb((data[16 + i] & 0xff), dev->mmio + 0x0);
+
+ /* Write 5 dummy values to xilinx */
+ for (i = 0; i < 5; i++)
+ writeb(0x00, dev->mmio + 0x0);
+
+ /* Test if there was an error during download -> INTB was thrown */
+ value = readl(dev_private->plx_regbase + PLX9052_INTCSR);
+ if (value & PLX9052_INTCSR_LI2STAT) {
+ /* Disable interrupt */
+ writel(0x00, dev_private->plx_regbase + PLX9052_INTCSR);
+ dev_err(dev->class_dev, "Xilinx download failed\n");
+ return -EIO;
+ }
+
+ /* Wait until the Xilinx is ready for real work */
+ sleep(1);
+
+ /* Enable PLX-Interrupts */
+ writel(PLX9052_INTCSR_LI1ENAB |
+ PLX9052_INTCSR_LI1POL |
+ PLX9052_INTCSR_PCIENAB,
+ dev_private->plx_regbase + PLX9052_INTCSR);
+
+ return 0;
+}
+
+static int me_reset(struct comedi_device *dev)
+{
+ struct me_private_data *dev_private = dev->private;
+
+ /* Reset board */
+ writew(0x00, dev->mmio + ME_CONTROL_1);
+ writew(0x00, dev->mmio + ME_CONTROL_2);
+ writew(0x00, dev->mmio + ME_RESET_INTERRUPT);
+ writew(0x00, dev->mmio + ME_DAC_CONTROL);
+
+ /* Save values in the board context */
+ dev_private->dac_control = 0;
+ dev_private->control_1 = 0;
+ dev_private->control_2 = 0;
+
+ return 0;
+}
+
+static int me_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct me_board *board = NULL;
+ struct me_private_data *dev_private;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(me_boards))
+ board = &me_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private));
+ if (!dev_private)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev_private->plx_regbase = pci_ioremap_bar(pcidev, 0);
+ if (!dev_private->plx_regbase)
+ return -ENOMEM;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 2);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ /* Download firmware and reset card */
+ if (board->needs_firmware) {
+ ret = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ ME2600_FIRMWARE,
+ me2600_xilinx_download, 0);
+ if (ret < 0)
+ return ret;
+ }
+ me_reset(dev);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 16;
+ s->range_table = &me_ai_range;
+ s->insn_read = me_ai_insn_read;
+
+ s = &dev->subdevices[1];
+ if (board->has_ao) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_COMMON;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 4;
+ s->range_table = &me_ao_range;
+ s->insn_write = me_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->len_chanlist = 32;
+ s->range_table = &range_digital;
+ s->insn_bits = me_dio_insn_bits;
+ s->insn_config = me_dio_insn_config;
+
+ return 0;
+}
+
+static void me_detach(struct comedi_device *dev)
+{
+ struct me_private_data *dev_private = dev->private;
+
+ if (dev_private) {
+ if (dev->mmio)
+ me_reset(dev);
+ if (dev_private->plx_regbase)
+ iounmap(dev_private->plx_regbase);
+ }
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver me_daq_driver = {
+ .driver_name = "me_daq",
+ .module = THIS_MODULE,
+ .auto_attach = me_auto_attach,
+ .detach = me_detach,
+};
+
+static int me_daq_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &me_daq_driver, id->driver_data);
+}
+
+static const struct pci_device_id me_daq_pci_table[] = {
+ { PCI_VDEVICE(MEILHAUS, 0x2600), BOARD_ME2600 },
+ { PCI_VDEVICE(MEILHAUS, 0x2000), BOARD_ME2000 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, me_daq_pci_table);
+
+static struct pci_driver me_daq_pci_driver = {
+ .name = "me_daq",
+ .id_table = me_daq_pci_table,
+ .probe = me_daq_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(me_daq_driver, me_daq_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
+/*(DEBLOBBED)*/
diff --git a/drivers/staging/comedi/drivers/mf6x4.c b/drivers/staging/comedi/drivers/mf6x4.c
new file mode 100644
index 000000000..a675e2ef9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mf6x4.c
@@ -0,0 +1,330 @@
+/*
+ * comedi/drivers/mf6x4.c
+ * Driver for Humusoft MF634 and MF624 Data acquisition cards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+/*
+ * Driver: mf6x4
+ * Description: Humusoft MF634 and MF624 Data acquisition card driver
+ * Devices: [Humusoft] MF634 (mf634), MF624 (mf624)
+ * Author: Rostislav Lisovy <lisovy@gmail.com>
+ * Status: works
+ * Updated:
+ * Configuration Options: none
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "../comedi_pci.h"
+
+/* Registers present in BAR0 memory region */
+#define MF624_GPIOC_R 0x54
+
+#define MF6X4_GPIOC_EOLC /* End Of Last Conversion */ (1 << 17)
+#define MF6X4_GPIOC_LDAC /* Load DACs */ (1 << 23)
+#define MF6X4_GPIOC_DACEN (1 << 26)
+
+/* BAR1 registers */
+#define MF6X4_DIN_R 0x10
+#define MF6X4_DIN_M 0xff
+#define MF6X4_DOUT_R 0x10
+#define MF6X4_DOUT_M 0xff
+
+#define MF6X4_ADSTART_R 0x20
+#define MF6X4_ADDATA_R 0x00
+#define MF6X4_ADCTRL_R 0x00
+#define MF6X4_ADCTRL_M 0xff
+
+#define MF6X4_DA0_R 0x20
+#define MF6X4_DA1_R 0x22
+#define MF6X4_DA2_R 0x24
+#define MF6X4_DA3_R 0x26
+#define MF6X4_DA4_R 0x28
+#define MF6X4_DA5_R 0x2a
+#define MF6X4_DA6_R 0x2c
+#define MF6X4_DA7_R 0x2e
+/* Map DAC cahnnel id to real HW-dependent offset value */
+#define MF6X4_DAC_R(x) (0x20 + ((x) * 2))
+
+/* BAR2 registers */
+#define MF634_GPIOC_R 0x68
+
+enum mf6x4_boardid {
+ BOARD_MF634,
+ BOARD_MF624,
+};
+
+struct mf6x4_board {
+ const char *name;
+ unsigned int bar_nums[3]; /* We need to keep track of the
+ order of BARs used by the cards */
+};
+
+static const struct mf6x4_board mf6x4_boards[] = {
+ [BOARD_MF634] = {
+ .name = "mf634",
+ .bar_nums = {0, 2, 3},
+ },
+ [BOARD_MF624] = {
+ .name = "mf624",
+ .bar_nums = {0, 2, 4},
+ },
+};
+
+struct mf6x4_private {
+ /*
+ * Documentation for both MF634 and MF624 describes registers
+ * present in BAR0, 1 and 2 regions.
+ * The real (i.e. in HW) BAR numbers are different for MF624
+ * and MF634 yet we will call them 0, 1, 2
+ */
+ void __iomem *bar0_mem;
+ void __iomem *bar2_mem;
+
+ /*
+ * This configuration register has the same function and fields
+ * for both cards however it lies in different BARs on different
+ * offsets -- this variable makes the access easier
+ */
+ void __iomem *gpioc_R;
+};
+
+static int mf6x4_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = ioread16(dev->mmio + MF6X4_DIN_R) & MF6X4_DIN_M;
+
+ return insn->n;
+}
+
+static int mf6x4_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ iowrite16(s->state & MF6X4_DOUT_M, dev->mmio + MF6X4_DOUT_R);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int mf6x4_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct mf6x4_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = ioread32(devpriv->gpioc_R);
+ if (status & MF6X4_GPIOC_EOLC)
+ return 0;
+ return -EBUSY;
+}
+
+static int mf6x4_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ int i;
+ int d;
+
+ /* Set the ADC channel number in the scan list */
+ iowrite16((1 << chan) & MF6X4_ADCTRL_M, dev->mmio + MF6X4_ADCTRL_R);
+
+ for (i = 0; i < insn->n; i++) {
+ /* Trigger ADC conversion by reading ADSTART */
+ ioread16(dev->mmio + MF6X4_ADSTART_R);
+
+ ret = comedi_timeout(dev, s, insn, mf6x4_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Read the actual value */
+ d = ioread16(dev->mmio + MF6X4_ADDATA_R);
+ d &= s->maxdata;
+ data[i] = d;
+ }
+
+ iowrite16(0x0, dev->mmio + MF6X4_ADCTRL_R);
+
+ return insn->n;
+}
+
+static int mf6x4_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct mf6x4_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ uint32_t gpioc;
+ int i;
+
+ /* Enable instantaneous update of converters outputs + Enable DACs */
+ gpioc = ioread32(devpriv->gpioc_R);
+ iowrite32((gpioc & ~MF6X4_GPIOC_LDAC) | MF6X4_GPIOC_DACEN,
+ devpriv->gpioc_R);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ iowrite16(val, dev->mmio + MF6X4_DAC_R(chan));
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int mf6x4_auto_attach(struct comedi_device *dev, unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct mf6x4_board *board = NULL;
+ struct mf6x4_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(mf6x4_boards))
+ board = &mf6x4_boards[context];
+ else
+ return -ENODEV;
+
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->bar0_mem = pci_ioremap_bar(pcidev, board->bar_nums[0]);
+ if (!devpriv->bar0_mem)
+ return -ENODEV;
+
+ dev->mmio = pci_ioremap_bar(pcidev, board->bar_nums[1]);
+ if (!dev->mmio)
+ return -ENODEV;
+
+ devpriv->bar2_mem = pci_ioremap_bar(pcidev, board->bar_nums[2]);
+ if (!devpriv->bar2_mem)
+ return -ENODEV;
+
+ if (board == &mf6x4_boards[BOARD_MF634])
+ devpriv->gpioc_R = devpriv->bar2_mem + MF634_GPIOC_R;
+ else
+ devpriv->gpioc_R = devpriv->bar0_mem + MF624_GPIOC_R;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* ADC */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->maxdata = 0x3fff; /* 14 bits ADC */
+ s->range_table = &range_bipolar10;
+ s->insn_read = mf6x4_ai_insn_read;
+
+ /* DAC */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 0x3fff; /* 14 bits DAC */
+ s->range_table = &range_bipolar10;
+ s->insn_write = mf6x4_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* DIN */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = mf6x4_di_insn_bits;
+
+ /* DOUT */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = mf6x4_do_insn_bits;
+
+ return 0;
+}
+
+static void mf6x4_detach(struct comedi_device *dev)
+{
+ struct mf6x4_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->bar0_mem)
+ iounmap(devpriv->bar0_mem);
+ if (devpriv->bar2_mem)
+ iounmap(devpriv->bar2_mem);
+ }
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver mf6x4_driver = {
+ .driver_name = "mf6x4",
+ .module = THIS_MODULE,
+ .auto_attach = mf6x4_auto_attach,
+ .detach = mf6x4_detach,
+};
+
+static int mf6x4_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &mf6x4_driver, id->driver_data);
+}
+
+static const struct pci_device_id mf6x4_pci_table[] = {
+ { PCI_VDEVICE(HUMUSOFT, 0x0634), BOARD_MF634 },
+ { PCI_VDEVICE(HUMUSOFT, 0x0624), BOARD_MF624 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, mf6x4_pci_table);
+
+static struct pci_driver mf6x4_pci_driver = {
+ .name = "mf6x4",
+ .id_table = mf6x4_pci_table,
+ .probe = mf6x4_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+
+module_comedi_pci_driver(mf6x4_driver, mf6x4_pci_driver);
+
+MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>");
+MODULE_DESCRIPTION("Comedi MF634 and MF624 DAQ cards driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/mite.c b/drivers/staging/comedi/drivers/mite.c
new file mode 100644
index 000000000..e43a0c832
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mite.c
@@ -0,0 +1,626 @@
+/*
+ comedi/drivers/mite.c
+ Hardware driver for NI Mite PCI interface chip
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+ The PCI-MIO E series driver was originally written by
+ Tomasz Motylewski <...>, and ported to comedi by ds.
+
+ References for specifications:
+
+ 321747b.pdf Register Level Programmer Manual (obsolete)
+ 321747c.pdf Register Level Programmer Manual (new)
+ DAQ-STC reference manual
+
+ Other possibly relevant info:
+
+ 320517c.pdf User manual (obsolete)
+ 320517f.pdf User manual (new)
+ 320889a.pdf delete
+ 320906c.pdf maximum signal ratings
+ 321066a.pdf about 16x
+ 321791a.pdf discontinuation of at-mio-16e-10 rev. c
+ 321808a.pdf about at-mio-16e-10 rev P
+ 321837a.pdf discontinuation of at-mio-16de-10 rev d
+ 321838a.pdf about at-mio-16de-10 rev N
+
+ ISSUES:
+
+*/
+
+/* #define USE_KMALLOC */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+
+#include "../comedi_pci.h"
+
+#include "mite.h"
+
+#define TOP_OF_PAGE(x) ((x)|(~(PAGE_MASK)))
+
+struct mite_struct *mite_alloc(struct pci_dev *pcidev)
+{
+ struct mite_struct *mite;
+ unsigned int i;
+
+ mite = kzalloc(sizeof(*mite), GFP_KERNEL);
+ if (mite) {
+ spin_lock_init(&mite->lock);
+ mite->pcidev = pcidev;
+ for (i = 0; i < MAX_MITE_DMA_CHANNELS; ++i) {
+ mite->channels[i].mite = mite;
+ mite->channels[i].channel = i;
+ mite->channels[i].done = 1;
+ }
+ }
+ return mite;
+}
+EXPORT_SYMBOL_GPL(mite_alloc);
+
+static void dump_chip_signature(u32 csigr_bits)
+{
+ pr_info("version = %i, type = %i, mite mode = %i, interface mode = %i\n",
+ mite_csigr_version(csigr_bits), mite_csigr_type(csigr_bits),
+ mite_csigr_mmode(csigr_bits), mite_csigr_imode(csigr_bits));
+ pr_info("num channels = %i, write post fifo depth = %i, wins = %i, iowins = %i\n",
+ mite_csigr_dmac(csigr_bits), mite_csigr_wpdep(csigr_bits),
+ mite_csigr_wins(csigr_bits), mite_csigr_iowins(csigr_bits));
+}
+
+static unsigned mite_fifo_size(struct mite_struct *mite, unsigned channel)
+{
+ unsigned fcr_bits = readl(mite->mite_io_addr + MITE_FCR(channel));
+ unsigned empty_count = (fcr_bits >> 16) & 0xff;
+ unsigned full_count = fcr_bits & 0xff;
+
+ return empty_count + full_count;
+}
+
+int mite_setup2(struct comedi_device *dev,
+ struct mite_struct *mite, bool use_win1)
+{
+ unsigned long length;
+ int i;
+ u32 csigr_bits;
+ unsigned unknown_dma_burst_bits;
+
+ pci_set_master(mite->pcidev);
+
+ mite->mite_io_addr = pci_ioremap_bar(mite->pcidev, 0);
+ if (!mite->mite_io_addr) {
+ dev_err(dev->class_dev,
+ "Failed to remap mite io memory address\n");
+ return -ENOMEM;
+ }
+ mite->mite_phys_addr = pci_resource_start(mite->pcidev, 0);
+
+ dev->mmio = pci_ioremap_bar(mite->pcidev, 1);
+ if (!dev->mmio) {
+ dev_err(dev->class_dev,
+ "Failed to remap daq io memory address\n");
+ return -ENOMEM;
+ }
+ mite->daq_phys_addr = pci_resource_start(mite->pcidev, 1);
+ length = pci_resource_len(mite->pcidev, 1);
+
+ if (use_win1) {
+ writel(0, mite->mite_io_addr + MITE_IODWBSR);
+ dev_info(dev->class_dev,
+ "using I/O Window Base Size register 1\n");
+ writel(mite->daq_phys_addr | WENAB |
+ MITE_IODWBSR_1_WSIZE_bits(length),
+ mite->mite_io_addr + MITE_IODWBSR_1);
+ writel(0, mite->mite_io_addr + MITE_IODWCR_1);
+ } else {
+ writel(mite->daq_phys_addr | WENAB,
+ mite->mite_io_addr + MITE_IODWBSR);
+ }
+ /*
+ * make sure dma bursts work. I got this from running a bus analyzer
+ * on a pxi-6281 and a pxi-6713. 6713 powered up with register value
+ * of 0x61f and bursts worked. 6281 powered up with register value of
+ * 0x1f and bursts didn't work. The NI windows driver reads the
+ * register, then does a bitwise-or of 0x600 with it and writes it back.
+ */
+ unknown_dma_burst_bits =
+ readl(mite->mite_io_addr + MITE_UNKNOWN_DMA_BURST_REG);
+ unknown_dma_burst_bits |= UNKNOWN_DMA_BURST_ENABLE_BITS;
+ writel(unknown_dma_burst_bits,
+ mite->mite_io_addr + MITE_UNKNOWN_DMA_BURST_REG);
+
+ csigr_bits = readl(mite->mite_io_addr + MITE_CSIGR);
+ mite->num_channels = mite_csigr_dmac(csigr_bits);
+ if (mite->num_channels > MAX_MITE_DMA_CHANNELS) {
+ dev_warn(dev->class_dev,
+ "mite: bug? chip claims to have %i dma channels. Setting to %i.\n",
+ mite->num_channels, MAX_MITE_DMA_CHANNELS);
+ mite->num_channels = MAX_MITE_DMA_CHANNELS;
+ }
+ dump_chip_signature(csigr_bits);
+ for (i = 0; i < mite->num_channels; i++) {
+ writel(CHOR_DMARESET, mite->mite_io_addr + MITE_CHOR(i));
+ /* disable interrupts */
+ writel(CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE |
+ CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE |
+ CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE,
+ mite->mite_io_addr + MITE_CHCR(i));
+ }
+ mite->fifo_size = mite_fifo_size(mite, 0);
+ dev_info(dev->class_dev, "fifo size is %i.\n", mite->fifo_size);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mite_setup2);
+
+void mite_detach(struct mite_struct *mite)
+{
+ if (!mite)
+ return;
+
+ if (mite->mite_io_addr)
+ iounmap(mite->mite_io_addr);
+
+ kfree(mite);
+}
+EXPORT_SYMBOL_GPL(mite_detach);
+
+struct mite_dma_descriptor_ring *mite_alloc_ring(struct mite_struct *mite)
+{
+ struct mite_dma_descriptor_ring *ring =
+ kmalloc(sizeof(struct mite_dma_descriptor_ring), GFP_KERNEL);
+
+ if (!ring)
+ return NULL;
+ ring->hw_dev = get_device(&mite->pcidev->dev);
+ if (!ring->hw_dev) {
+ kfree(ring);
+ return NULL;
+ }
+ ring->n_links = 0;
+ ring->descriptors = NULL;
+ ring->descriptors_dma_addr = 0;
+ return ring;
+};
+EXPORT_SYMBOL_GPL(mite_alloc_ring);
+
+void mite_free_ring(struct mite_dma_descriptor_ring *ring)
+{
+ if (ring) {
+ if (ring->descriptors) {
+ dma_free_coherent(ring->hw_dev,
+ ring->n_links *
+ sizeof(struct mite_dma_descriptor),
+ ring->descriptors,
+ ring->descriptors_dma_addr);
+ }
+ put_device(ring->hw_dev);
+ kfree(ring);
+ }
+};
+EXPORT_SYMBOL_GPL(mite_free_ring);
+
+struct mite_channel *mite_request_channel_in_range(struct mite_struct *mite,
+ struct
+ mite_dma_descriptor_ring
+ *ring, unsigned min_channel,
+ unsigned max_channel)
+{
+ int i;
+ unsigned long flags;
+ struct mite_channel *channel = NULL;
+
+ /* spin lock so mite_release_channel can be called safely
+ * from interrupts
+ */
+ spin_lock_irqsave(&mite->lock, flags);
+ for (i = min_channel; i <= max_channel; ++i) {
+ if (mite->channel_allocated[i] == 0) {
+ mite->channel_allocated[i] = 1;
+ channel = &mite->channels[i];
+ channel->ring = ring;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&mite->lock, flags);
+ return channel;
+}
+EXPORT_SYMBOL_GPL(mite_request_channel_in_range);
+
+void mite_release_channel(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+ unsigned long flags;
+
+ /* spin lock to prevent races with mite_request_channel */
+ spin_lock_irqsave(&mite->lock, flags);
+ if (mite->channel_allocated[mite_chan->channel]) {
+ mite_dma_disarm(mite_chan);
+ mite_dma_reset(mite_chan);
+ /*
+ * disable all channel's interrupts (do it after disarm/reset so
+ * MITE_CHCR reg isn't changed while dma is still active!)
+ */
+ writel(CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE |
+ CHCR_CLR_SAR_IE | CHCR_CLR_DONE_IE |
+ CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE |
+ CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE,
+ mite->mite_io_addr + MITE_CHCR(mite_chan->channel));
+ mite->channel_allocated[mite_chan->channel] = 0;
+ mite_chan->ring = NULL;
+ mmiowb();
+ }
+ spin_unlock_irqrestore(&mite->lock, flags);
+}
+EXPORT_SYMBOL_GPL(mite_release_channel);
+
+void mite_dma_arm(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+ int chor;
+ unsigned long flags;
+
+ /*
+ * memory barrier is intended to insure any twiddling with the buffer
+ * is done before writing to the mite to arm dma transfer
+ */
+ smp_mb();
+ /* arm */
+ chor = CHOR_START;
+ spin_lock_irqsave(&mite->lock, flags);
+ mite_chan->done = 0;
+ writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel));
+ mmiowb();
+ spin_unlock_irqrestore(&mite->lock, flags);
+/* mite_dma_tcr(mite, channel); */
+}
+EXPORT_SYMBOL_GPL(mite_dma_arm);
+
+/**************************************/
+
+int mite_buf_change(struct mite_dma_descriptor_ring *ring,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ unsigned int n_links;
+ int i;
+
+ if (ring->descriptors) {
+ dma_free_coherent(ring->hw_dev,
+ ring->n_links *
+ sizeof(struct mite_dma_descriptor),
+ ring->descriptors,
+ ring->descriptors_dma_addr);
+ }
+ ring->descriptors = NULL;
+ ring->descriptors_dma_addr = 0;
+ ring->n_links = 0;
+
+ if (async->prealloc_bufsz == 0)
+ return 0;
+
+ n_links = async->prealloc_bufsz >> PAGE_SHIFT;
+
+ ring->descriptors =
+ dma_alloc_coherent(ring->hw_dev,
+ n_links * sizeof(struct mite_dma_descriptor),
+ &ring->descriptors_dma_addr, GFP_KERNEL);
+ if (!ring->descriptors) {
+ dev_err(s->device->class_dev,
+ "mite: ring buffer allocation failed\n");
+ return -ENOMEM;
+ }
+ ring->n_links = n_links;
+
+ for (i = 0; i < n_links; i++) {
+ ring->descriptors[i].count = cpu_to_le32(PAGE_SIZE);
+ ring->descriptors[i].addr =
+ cpu_to_le32(async->buf_map->page_list[i].dma_addr);
+ ring->descriptors[i].next =
+ cpu_to_le32(ring->descriptors_dma_addr + (i +
+ 1) *
+ sizeof(struct mite_dma_descriptor));
+ }
+ ring->descriptors[n_links - 1].next =
+ cpu_to_le32(ring->descriptors_dma_addr);
+ /*
+ * barrier is meant to insure that all the writes to the dma descriptors
+ * have completed before the dma controller is commanded to read them
+ */
+ smp_wmb();
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mite_buf_change);
+
+void mite_prep_dma(struct mite_channel *mite_chan,
+ unsigned int num_device_bits, unsigned int num_memory_bits)
+{
+ unsigned int chor, chcr, mcr, dcr, lkcr;
+ struct mite_struct *mite = mite_chan->mite;
+
+ /* reset DMA and FIFO */
+ chor = CHOR_DMARESET | CHOR_FRESET;
+ writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel));
+
+ /* short link chaining mode */
+ chcr = CHCR_SET_DMA_IE | CHCR_LINKSHORT | CHCR_SET_DONE_IE |
+ CHCR_BURSTEN;
+ /*
+ * Link Complete Interrupt: interrupt every time a link
+ * in MITE_RING is completed. This can generate a lot of
+ * extra interrupts, but right now we update the values
+ * of buf_int_ptr and buf_int_count at each interrupt. A
+ * better method is to poll the MITE before each user
+ * "read()" to calculate the number of bytes available.
+ */
+ chcr |= CHCR_SET_LC_IE;
+ if (num_memory_bits == 32 && num_device_bits == 16) {
+ /*
+ * Doing a combined 32 and 16 bit byteswap gets the 16 bit
+ * samples into the fifo in the right order. Tested doing 32 bit
+ * memory to 16 bit device transfers to the analog out of a
+ * pxi-6281, which has mite version = 1, type = 4. This also
+ * works for dma reads from the counters on e-series boards.
+ */
+ chcr |= CHCR_BYTE_SWAP_DEVICE | CHCR_BYTE_SWAP_MEMORY;
+ }
+ if (mite_chan->dir == COMEDI_INPUT)
+ chcr |= CHCR_DEV_TO_MEM;
+
+ writel(chcr, mite->mite_io_addr + MITE_CHCR(mite_chan->channel));
+
+ /* to/from memory */
+ mcr = CR_RL(64) | CR_ASEQUP;
+ switch (num_memory_bits) {
+ case 8:
+ mcr |= CR_PSIZE8;
+ break;
+ case 16:
+ mcr |= CR_PSIZE16;
+ break;
+ case 32:
+ mcr |= CR_PSIZE32;
+ break;
+ default:
+ pr_warn("bug! invalid mem bit width for dma transfer\n");
+ break;
+ }
+ writel(mcr, mite->mite_io_addr + MITE_MCR(mite_chan->channel));
+
+ /* from/to device */
+ dcr = CR_RL(64) | CR_ASEQUP;
+ dcr |= CR_PORTIO | CR_AMDEVICE | CR_REQSDRQ(mite_chan->channel);
+ switch (num_device_bits) {
+ case 8:
+ dcr |= CR_PSIZE8;
+ break;
+ case 16:
+ dcr |= CR_PSIZE16;
+ break;
+ case 32:
+ dcr |= CR_PSIZE32;
+ break;
+ default:
+ pr_warn("bug! invalid dev bit width for dma transfer\n");
+ break;
+ }
+ writel(dcr, mite->mite_io_addr + MITE_DCR(mite_chan->channel));
+
+ /* reset the DAR */
+ writel(0, mite->mite_io_addr + MITE_DAR(mite_chan->channel));
+
+ /* the link is 32bits */
+ lkcr = CR_RL(64) | CR_ASEQUP | CR_PSIZE32;
+ writel(lkcr, mite->mite_io_addr + MITE_LKCR(mite_chan->channel));
+
+ /* starting address for link chaining */
+ writel(mite_chan->ring->descriptors_dma_addr,
+ mite->mite_io_addr + MITE_LKAR(mite_chan->channel));
+}
+EXPORT_SYMBOL_GPL(mite_prep_dma);
+
+static u32 mite_device_bytes_transferred(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+
+ return readl(mite->mite_io_addr + MITE_DAR(mite_chan->channel));
+}
+
+u32 mite_bytes_in_transit(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+
+ return readl(mite->mite_io_addr +
+ MITE_FCR(mite_chan->channel)) & 0x000000FF;
+}
+EXPORT_SYMBOL_GPL(mite_bytes_in_transit);
+
+/* returns lower bound for number of bytes transferred from device to memory */
+u32 mite_bytes_written_to_memory_lb(struct mite_channel *mite_chan)
+{
+ u32 device_byte_count;
+
+ device_byte_count = mite_device_bytes_transferred(mite_chan);
+ return device_byte_count - mite_bytes_in_transit(mite_chan);
+}
+EXPORT_SYMBOL_GPL(mite_bytes_written_to_memory_lb);
+
+/* returns upper bound for number of bytes transferred from device to memory */
+u32 mite_bytes_written_to_memory_ub(struct mite_channel *mite_chan)
+{
+ u32 in_transit_count;
+
+ in_transit_count = mite_bytes_in_transit(mite_chan);
+ return mite_device_bytes_transferred(mite_chan) - in_transit_count;
+}
+EXPORT_SYMBOL_GPL(mite_bytes_written_to_memory_ub);
+
+/* returns lower bound for number of bytes read from memory to device */
+u32 mite_bytes_read_from_memory_lb(struct mite_channel *mite_chan)
+{
+ u32 device_byte_count;
+
+ device_byte_count = mite_device_bytes_transferred(mite_chan);
+ return device_byte_count + mite_bytes_in_transit(mite_chan);
+}
+EXPORT_SYMBOL_GPL(mite_bytes_read_from_memory_lb);
+
+/* returns upper bound for number of bytes read from memory to device */
+u32 mite_bytes_read_from_memory_ub(struct mite_channel *mite_chan)
+{
+ u32 in_transit_count;
+
+ in_transit_count = mite_bytes_in_transit(mite_chan);
+ return mite_device_bytes_transferred(mite_chan) + in_transit_count;
+}
+EXPORT_SYMBOL_GPL(mite_bytes_read_from_memory_ub);
+
+unsigned mite_dma_tcr(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+
+ return readl(mite->mite_io_addr + MITE_TCR(mite_chan->channel));
+}
+EXPORT_SYMBOL_GPL(mite_dma_tcr);
+
+void mite_dma_disarm(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+ unsigned chor;
+
+ /* disarm */
+ chor = CHOR_ABORT;
+ writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel));
+}
+EXPORT_SYMBOL_GPL(mite_dma_disarm);
+
+int mite_sync_input_dma(struct mite_channel *mite_chan,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ int count;
+ unsigned int nbytes, old_alloc_count;
+
+ old_alloc_count = async->buf_write_alloc_count;
+ /* write alloc as much as we can */
+ comedi_buf_write_alloc(s, async->prealloc_bufsz);
+
+ nbytes = mite_bytes_written_to_memory_lb(mite_chan);
+ if ((int)(mite_bytes_written_to_memory_ub(mite_chan) -
+ old_alloc_count) > 0) {
+ dev_warn(s->device->class_dev,
+ "mite: DMA overwrite of free area\n");
+ async->events |= COMEDI_CB_OVERFLOW;
+ return -1;
+ }
+
+ count = nbytes - async->buf_write_count;
+ /* it's possible count will be negative due to
+ * conservative value returned by mite_bytes_written_to_memory_lb */
+ if (count <= 0)
+ return 0;
+
+ comedi_buf_write_free(s, count);
+ comedi_inc_scan_progress(s, count);
+ async->events |= COMEDI_CB_BLOCK;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mite_sync_input_dma);
+
+int mite_sync_output_dma(struct mite_channel *mite_chan,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ u32 stop_count = cmd->stop_arg * comedi_bytes_per_scan(s);
+ unsigned int old_alloc_count = async->buf_read_alloc_count;
+ u32 nbytes_ub, nbytes_lb;
+ int count;
+
+ /* read alloc as much as we can */
+ comedi_buf_read_alloc(s, async->prealloc_bufsz);
+ nbytes_lb = mite_bytes_read_from_memory_lb(mite_chan);
+ if (cmd->stop_src == TRIG_COUNT && (int)(nbytes_lb - stop_count) > 0)
+ nbytes_lb = stop_count;
+ nbytes_ub = mite_bytes_read_from_memory_ub(mite_chan);
+ if (cmd->stop_src == TRIG_COUNT && (int)(nbytes_ub - stop_count) > 0)
+ nbytes_ub = stop_count;
+ if ((int)(nbytes_ub - old_alloc_count) > 0) {
+ dev_warn(s->device->class_dev, "mite: DMA underrun\n");
+ async->events |= COMEDI_CB_OVERFLOW;
+ return -1;
+ }
+ count = nbytes_lb - async->buf_read_count;
+ if (count <= 0)
+ return 0;
+
+ if (count) {
+ comedi_buf_read_free(s, count);
+ async->events |= COMEDI_CB_BLOCK;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mite_sync_output_dma);
+
+unsigned mite_get_status(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+ unsigned status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mite->lock, flags);
+ status = readl(mite->mite_io_addr + MITE_CHSR(mite_chan->channel));
+ if (status & CHSR_DONE) {
+ mite_chan->done = 1;
+ writel(CHOR_CLRDONE,
+ mite->mite_io_addr + MITE_CHOR(mite_chan->channel));
+ }
+ mmiowb();
+ spin_unlock_irqrestore(&mite->lock, flags);
+ return status;
+}
+EXPORT_SYMBOL_GPL(mite_get_status);
+
+int mite_done(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+ unsigned long flags;
+ int done;
+
+ mite_get_status(mite_chan);
+ spin_lock_irqsave(&mite->lock, flags);
+ done = mite_chan->done;
+ spin_unlock_irqrestore(&mite->lock, flags);
+ return done;
+}
+EXPORT_SYMBOL_GPL(mite_done);
+
+static int __init mite_module_init(void)
+{
+ return 0;
+}
+
+static void __exit mite_module_exit(void)
+{
+}
+
+module_init(mite_module_init);
+module_exit(mite_module_exit);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/mite.h b/drivers/staging/comedi/drivers/mite.h
new file mode 100644
index 000000000..b3ca7fc3a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mite.h
@@ -0,0 +1,347 @@
+/*
+ module/mite.h
+ Hardware driver for NI Mite PCI interface chip
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _MITE_H_
+#define _MITE_H_
+
+#include <linux/log2.h>
+#include <linux/slab.h>
+#include "../comedi_pci.h"
+
+#define PCIMIO_COMPAT
+
+#define MAX_MITE_DMA_CHANNELS 8
+
+struct mite_dma_descriptor {
+ __le32 count;
+ __le32 addr;
+ __le32 next;
+ u32 dar;
+};
+
+struct mite_dma_descriptor_ring {
+ struct device *hw_dev;
+ unsigned int n_links;
+ struct mite_dma_descriptor *descriptors;
+ dma_addr_t descriptors_dma_addr;
+};
+
+struct mite_channel {
+ struct mite_struct *mite;
+ unsigned channel;
+ int dir;
+ int done;
+ struct mite_dma_descriptor_ring *ring;
+};
+
+struct mite_struct {
+ struct pci_dev *pcidev;
+ resource_size_t mite_phys_addr;
+ void __iomem *mite_io_addr;
+ resource_size_t daq_phys_addr;
+ struct mite_channel channels[MAX_MITE_DMA_CHANNELS];
+ short channel_allocated[MAX_MITE_DMA_CHANNELS];
+ int num_channels;
+ unsigned fifo_size;
+ spinlock_t lock;
+};
+
+struct mite_struct *mite_alloc(struct pci_dev *pcidev);
+
+int mite_setup2(struct comedi_device *, struct mite_struct *, bool use_win1);
+
+static inline int mite_setup(struct comedi_device *dev,
+ struct mite_struct *mite)
+{
+ return mite_setup2(dev, mite, false);
+}
+
+void mite_detach(struct mite_struct *mite);
+struct mite_dma_descriptor_ring *mite_alloc_ring(struct mite_struct *mite);
+void mite_free_ring(struct mite_dma_descriptor_ring *ring);
+struct mite_channel *mite_request_channel_in_range(struct mite_struct *mite,
+ struct
+ mite_dma_descriptor_ring
+ *ring, unsigned min_channel,
+ unsigned max_channel);
+static inline struct mite_channel *mite_request_channel(struct mite_struct
+ *mite,
+ struct
+ mite_dma_descriptor_ring
+ *ring)
+{
+ return mite_request_channel_in_range(mite, ring, 0,
+ mite->num_channels - 1);
+}
+
+void mite_release_channel(struct mite_channel *mite_chan);
+
+unsigned mite_dma_tcr(struct mite_channel *mite_chan);
+void mite_dma_arm(struct mite_channel *mite_chan);
+void mite_dma_disarm(struct mite_channel *mite_chan);
+int mite_sync_input_dma(struct mite_channel *mite_chan,
+ struct comedi_subdevice *s);
+int mite_sync_output_dma(struct mite_channel *mite_chan,
+ struct comedi_subdevice *s);
+u32 mite_bytes_written_to_memory_lb(struct mite_channel *mite_chan);
+u32 mite_bytes_written_to_memory_ub(struct mite_channel *mite_chan);
+u32 mite_bytes_read_from_memory_lb(struct mite_channel *mite_chan);
+u32 mite_bytes_read_from_memory_ub(struct mite_channel *mite_chan);
+u32 mite_bytes_in_transit(struct mite_channel *mite_chan);
+unsigned mite_get_status(struct mite_channel *mite_chan);
+int mite_done(struct mite_channel *mite_chan);
+
+void mite_prep_dma(struct mite_channel *mite_chan,
+ unsigned int num_device_bits, unsigned int num_memory_bits);
+int mite_buf_change(struct mite_dma_descriptor_ring *ring,
+ struct comedi_subdevice *s);
+
+enum mite_registers {
+ /* The bits 0x90180700 in MITE_UNKNOWN_DMA_BURST_REG can be
+ written and read back. The bits 0x1f always read as 1.
+ The rest always read as zero. */
+ MITE_UNKNOWN_DMA_BURST_REG = 0x28,
+ MITE_IODWBSR = 0xc0, /* IO Device Window Base Size Register */
+ MITE_IODWBSR_1 = 0xc4, /* IO Device Window Base Size Register 1 */
+ MITE_IODWCR_1 = 0xf4,
+ MITE_PCI_CONFIG_OFFSET = 0x300,
+ MITE_CSIGR = 0x460 /* chip signature */
+};
+
+#define MITE_CHAN(x) (0x500 + 0x100 * (x))
+#define MITE_CHOR(x) (0x00 + MITE_CHAN(x)) /* channel operation */
+#define MITE_CHCR(x) (0x04 + MITE_CHAN(x)) /* channel control */
+#define MITE_TCR(x) (0x08 + MITE_CHAN(x)) /* transfer count */
+#define MITE_MCR(x) (0x0c + MITE_CHAN(x)) /* memory configuration */
+#define MITE_MAR(x) (0x10 + MITE_CHAN(x)) /* memory address */
+#define MITE_DCR(x) (0x14 + MITE_CHAN(x)) /* device configuration */
+#define MITE_DAR(x) (0x18 + MITE_CHAN(x)) /* device address */
+#define MITE_LKCR(x) (0x1c + MITE_CHAN(x)) /* link configuration */
+#define MITE_LKAR(x) (0x20 + MITE_CHAN(x)) /* link address */
+#define MITE_LLKAR(x) (0x24 + MITE_CHAN(x)) /* see tnt5002 manual */
+#define MITE_BAR(x) (0x28 + MITE_CHAN(x)) /* base address */
+#define MITE_BCR(x) (0x2c + MITE_CHAN(x)) /* base count */
+#define MITE_SAR(x) (0x30 + MITE_CHAN(x)) /* ? address */
+#define MITE_WSCR(x) (0x34 + MITE_CHAN(x)) /* ? */
+#define MITE_WSER(x) (0x38 + MITE_CHAN(x)) /* ? */
+#define MITE_CHSR(x) (0x3c + MITE_CHAN(x)) /* channel status */
+#define MITE_FCR(x) (0x40 + MITE_CHAN(x)) /* fifo count */
+
+enum MITE_IODWBSR_bits {
+ WENAB = 0x80, /* window enable */
+};
+
+static inline unsigned MITE_IODWBSR_1_WSIZE_bits(unsigned size)
+{
+ unsigned order = 0;
+
+ BUG_ON(size == 0);
+ order = ilog2(size);
+ BUG_ON(order < 1);
+ return (order - 1) & 0x1f;
+}
+
+enum MITE_UNKNOWN_DMA_BURST_bits {
+ UNKNOWN_DMA_BURST_ENABLE_BITS = 0x600
+};
+
+static inline int mite_csigr_version(u32 csigr_bits)
+{
+ return csigr_bits & 0xf;
+};
+
+static inline int mite_csigr_type(u32 csigr_bits)
+{ /* original mite = 0, minimite = 1 */
+ return (csigr_bits >> 4) & 0xf;
+};
+
+static inline int mite_csigr_mmode(u32 csigr_bits)
+{ /* mite mode, minimite = 1 */
+ return (csigr_bits >> 8) & 0x3;
+};
+
+static inline int mite_csigr_imode(u32 csigr_bits)
+{ /* cpu port interface mode, pci = 0x3 */
+ return (csigr_bits >> 12) & 0x3;
+};
+
+static inline int mite_csigr_dmac(u32 csigr_bits)
+{ /* number of dma channels */
+ return (csigr_bits >> 16) & 0xf;
+};
+
+static inline int mite_csigr_wpdep(u32 csigr_bits)
+{ /* write post fifo depth */
+ unsigned int wpdep_bits = (csigr_bits >> 20) & 0x7;
+
+ return (wpdep_bits) ? (1 << (wpdep_bits - 1)) : 0;
+}
+
+static inline int mite_csigr_wins(u32 csigr_bits)
+{
+ return (csigr_bits >> 24) & 0x1f;
+};
+
+static inline int mite_csigr_iowins(u32 csigr_bits)
+{ /* number of io windows */
+ return (csigr_bits >> 29) & 0x7;
+};
+
+enum MITE_MCR_bits {
+ MCRPON = 0,
+};
+
+enum MITE_DCR_bits {
+ DCR_NORMAL = (1 << 29),
+ DCRPON = 0,
+};
+
+enum MITE_CHOR_bits {
+ CHOR_DMARESET = (1 << 31),
+ CHOR_SET_SEND_TC = (1 << 11),
+ CHOR_CLR_SEND_TC = (1 << 10),
+ CHOR_SET_LPAUSE = (1 << 9),
+ CHOR_CLR_LPAUSE = (1 << 8),
+ CHOR_CLRDONE = (1 << 7),
+ CHOR_CLRRB = (1 << 6),
+ CHOR_CLRLC = (1 << 5),
+ CHOR_FRESET = (1 << 4),
+ CHOR_ABORT = (1 << 3), /* stop without emptying fifo */
+ CHOR_STOP = (1 << 2), /* stop after emptying fifo */
+ CHOR_CONT = (1 << 1),
+ CHOR_START = (1 << 0),
+ CHOR_PON = (CHOR_CLR_SEND_TC | CHOR_CLR_LPAUSE),
+};
+
+enum MITE_CHCR_bits {
+ CHCR_SET_DMA_IE = (1 << 31),
+ CHCR_CLR_DMA_IE = (1 << 30),
+ CHCR_SET_LINKP_IE = (1 << 29),
+ CHCR_CLR_LINKP_IE = (1 << 28),
+ CHCR_SET_SAR_IE = (1 << 27),
+ CHCR_CLR_SAR_IE = (1 << 26),
+ CHCR_SET_DONE_IE = (1 << 25),
+ CHCR_CLR_DONE_IE = (1 << 24),
+ CHCR_SET_MRDY_IE = (1 << 23),
+ CHCR_CLR_MRDY_IE = (1 << 22),
+ CHCR_SET_DRDY_IE = (1 << 21),
+ CHCR_CLR_DRDY_IE = (1 << 20),
+ CHCR_SET_LC_IE = (1 << 19),
+ CHCR_CLR_LC_IE = (1 << 18),
+ CHCR_SET_CONT_RB_IE = (1 << 17),
+ CHCR_CLR_CONT_RB_IE = (1 << 16),
+ CHCR_FIFODIS = (1 << 15),
+ CHCR_FIFO_ON = 0,
+ CHCR_BURSTEN = (1 << 14),
+ CHCR_NO_BURSTEN = 0,
+ CHCR_BYTE_SWAP_DEVICE = (1 << 6),
+ CHCR_BYTE_SWAP_MEMORY = (1 << 4),
+ CHCR_DIR = (1 << 3),
+ CHCR_DEV_TO_MEM = CHCR_DIR,
+ CHCR_MEM_TO_DEV = 0,
+ CHCR_NORMAL = (0 << 0),
+ CHCR_CONTINUE = (1 << 0),
+ CHCR_RINGBUFF = (2 << 0),
+ CHCR_LINKSHORT = (4 << 0),
+ CHCR_LINKLONG = (5 << 0),
+ CHCRPON =
+ (CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE |
+ CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE |
+ CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE),
+};
+
+enum ConfigRegister_bits {
+ CR_REQS_MASK = 0x7 << 16,
+ CR_ASEQDONT = 0x0 << 10,
+ CR_ASEQUP = 0x1 << 10,
+ CR_ASEQDOWN = 0x2 << 10,
+ CR_ASEQ_MASK = 0x3 << 10,
+ CR_PSIZE8 = (1 << 8),
+ CR_PSIZE16 = (2 << 8),
+ CR_PSIZE32 = (3 << 8),
+ CR_PORTCPU = (0 << 6),
+ CR_PORTIO = (1 << 6),
+ CR_PORTVXI = (2 << 6),
+ CR_PORTMXI = (3 << 6),
+ CR_AMDEVICE = (1 << 0),
+};
+static inline int CR_REQS(int source)
+{
+ return (source & 0x7) << 16;
+};
+
+static inline int CR_REQSDRQ(unsigned drq_line)
+{
+ /* This also works on m-series when
+ using channels (drq_line) 4 or 5. */
+ return CR_REQS((drq_line & 0x3) | 0x4);
+}
+
+static inline int CR_RL(unsigned int retry_limit)
+{
+ int value = 0;
+
+ if (retry_limit)
+ value = 1 + ilog2(retry_limit);
+ if (value > 0x7)
+ value = 0x7;
+ return (value & 0x7) << 21;
+}
+
+enum CHSR_bits {
+ CHSR_INT = (1 << 31),
+ CHSR_LPAUSES = (1 << 29),
+ CHSR_SARS = (1 << 27),
+ CHSR_DONE = (1 << 25),
+ CHSR_MRDY = (1 << 23),
+ CHSR_DRDY = (1 << 21),
+ CHSR_LINKC = (1 << 19),
+ CHSR_CONTS_RB = (1 << 17),
+ CHSR_ERROR = (1 << 15),
+ CHSR_SABORT = (1 << 14),
+ CHSR_HABORT = (1 << 13),
+ CHSR_STOPS = (1 << 12),
+ CHSR_OPERR_mask = (3 << 10),
+ CHSR_OPERR_NOERROR = (0 << 10),
+ CHSR_OPERR_FIFOERROR = (1 << 10),
+ CHSR_OPERR_LINKERROR = (1 << 10), /* ??? */
+ CHSR_XFERR = (1 << 9),
+ CHSR_END = (1 << 8),
+ CHSR_DRQ1 = (1 << 7),
+ CHSR_DRQ0 = (1 << 6),
+ CHSR_LxERR_mask = (3 << 4),
+ CHSR_LBERR = (1 << 4),
+ CHSR_LRERR = (2 << 4),
+ CHSR_LOERR = (3 << 4),
+ CHSR_MxERR_mask = (3 << 2),
+ CHSR_MBERR = (1 << 2),
+ CHSR_MRERR = (2 << 2),
+ CHSR_MOERR = (3 << 2),
+ CHSR_DxERR_mask = (3 << 0),
+ CHSR_DBERR = (1 << 0),
+ CHSR_DRERR = (2 << 0),
+ CHSR_DOERR = (3 << 0),
+};
+
+static inline void mite_dma_reset(struct mite_channel *mite_chan)
+{
+ writel(CHOR_DMARESET | CHOR_FRESET,
+ mite_chan->mite->mite_io_addr + MITE_CHOR(mite_chan->channel));
+};
+
+#endif
diff --git a/drivers/staging/comedi/drivers/mpc624.c b/drivers/staging/comedi/drivers/mpc624.c
new file mode 100644
index 000000000..0207b8edf
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mpc624.c
@@ -0,0 +1,357 @@
+/*
+ comedi/drivers/mpc624.c
+ Hardware driver for a Micro/sys inc. MPC-624 PC/104 board
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: mpc624
+Description: Micro/sys MPC-624 PC/104 board
+Devices: [Micro/sys] MPC-624 (mpc624)
+Author: Stanislaw Raczynski <sraczynski@op.pl>
+Updated: Thu, 15 Sep 2005 12:01:18 +0200
+Status: working
+
+ The Micro/sys MPC-624 board is based on the LTC2440 24-bit sigma-delta
+ ADC chip.
+
+ Subdevices supported by the driver:
+ - Analog In: supported
+ - Digital I/O: not supported
+ - LEDs: not supported
+ - EEPROM: not supported
+
+Configuration Options:
+ [0] - I/O base address
+ [1] - conversion rate
+ Conversion rate RMS noise Effective Number Of Bits
+ 0 3.52kHz 23uV 17
+ 1 1.76kHz 3.5uV 20
+ 2 880Hz 2uV 21.3
+ 3 440Hz 1.4uV 21.8
+ 4 220Hz 1uV 22.4
+ 5 110Hz 750uV 22.9
+ 6 55Hz 510nV 23.4
+ 7 27.5Hz 375nV 24
+ 8 13.75Hz 250nV 24.4
+ 9 6.875Hz 200nV 24.6
+ [2] - voltage range
+ 0 -1.01V .. +1.01V
+ 1 -10.1V .. +10.1V
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+/* Offsets of different ports */
+#define MPC624_MASTER_CONTROL 0 /* not used */
+#define MPC624_GNMUXCH 1 /* Gain, Mux, Channel of ADC */
+#define MPC624_ADC 2 /* read/write to/from ADC */
+#define MPC624_EE 3 /* read/write to/from serial EEPROM via I2C */
+#define MPC624_LEDS 4 /* write to LEDs */
+#define MPC624_DIO 5 /* read/write to/from digital I/O ports */
+#define MPC624_IRQ_MASK 6 /* IRQ masking enable/disable */
+
+/* Register bits' names */
+#define MPC624_ADBUSY (1<<5)
+#define MPC624_ADSDO (1<<4)
+#define MPC624_ADFO (1<<3)
+#define MPC624_ADCS (1<<2)
+#define MPC624_ADSCK (1<<1)
+#define MPC624_ADSDI (1<<0)
+
+/* SDI Speed/Resolution Programming bits */
+#define MPC624_OSR4 (1<<31)
+#define MPC624_OSR3 (1<<30)
+#define MPC624_OSR2 (1<<29)
+#define MPC624_OSR1 (1<<28)
+#define MPC624_OSR0 (1<<27)
+
+/* 32-bit output value bits' names */
+#define MPC624_EOC_BIT (1<<31)
+#define MPC624_DMY_BIT (1<<30)
+#define MPC624_SGN_BIT (1<<29)
+
+/* Conversion speeds */
+/* OSR4 OSR3 OSR2 OSR1 OSR0 Conversion rate RMS noise ENOB^
+ * X 0 0 0 1 3.52kHz 23uV 17
+ * X 0 0 1 0 1.76kHz 3.5uV 20
+ * X 0 0 1 1 880Hz 2uV 21.3
+ * X 0 1 0 0 440Hz 1.4uV 21.8
+ * X 0 1 0 1 220Hz 1uV 22.4
+ * X 0 1 1 0 110Hz 750uV 22.9
+ * X 0 1 1 1 55Hz 510nV 23.4
+ * X 1 0 0 0 27.5Hz 375nV 24
+ * X 1 0 0 1 13.75Hz 250nV 24.4
+ * X 1 1 1 1 6.875Hz 200nV 24.6
+ *
+ * ^ - Effective Number Of Bits
+ */
+
+#define MPC624_SPEED_3_52_kHz (MPC624_OSR4 | MPC624_OSR0)
+#define MPC624_SPEED_1_76_kHz (MPC624_OSR4 | MPC624_OSR1)
+#define MPC624_SPEED_880_Hz (MPC624_OSR4 | MPC624_OSR1 | MPC624_OSR0)
+#define MPC624_SPEED_440_Hz (MPC624_OSR4 | MPC624_OSR2)
+#define MPC624_SPEED_220_Hz (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR0)
+#define MPC624_SPEED_110_Hz (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR1)
+#define MPC624_SPEED_55_Hz \
+ (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR1 | MPC624_OSR0)
+#define MPC624_SPEED_27_5_Hz (MPC624_OSR4 | MPC624_OSR3)
+#define MPC624_SPEED_13_75_Hz (MPC624_OSR4 | MPC624_OSR3 | MPC624_OSR0)
+#define MPC624_SPEED_6_875_Hz \
+ (MPC624_OSR4 | MPC624_OSR3 | MPC624_OSR2 | MPC624_OSR1 | MPC624_OSR0)
+/* -------------------------------------------------------------------------- */
+struct mpc624_private {
+ /* set by mpc624_attach() from driver's parameters */
+ unsigned long int ulConvertionRate;
+};
+
+/* -------------------------------------------------------------------------- */
+static const struct comedi_lrange range_mpc624_bipolar1 = {
+ 1,
+ {
+/* BIP_RANGE(1.01) this is correct, */
+ /* but my MPC-624 actually seems to have a range of 2.02 */
+ BIP_RANGE(2.02)
+ }
+};
+
+static const struct comedi_lrange range_mpc624_bipolar10 = {
+ 1,
+ {
+/* BIP_RANGE(10.1) this is correct, */
+ /* but my MPC-624 actually seems to have a range of 20.2 */
+ BIP_RANGE(20.2)
+ }
+};
+
+static int mpc624_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + MPC624_ADC);
+ if ((status & MPC624_ADBUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int mpc624_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct mpc624_private *devpriv = dev->private;
+ int n, i;
+ unsigned long int data_in, data_out;
+ int ret;
+
+ /*
+ * WARNING:
+ * We always write 0 to GNSWA bit, so the channel range is +-/10.1Vdc
+ */
+ outb(insn->chanspec, dev->iobase + MPC624_GNMUXCH);
+
+ for (n = 0; n < insn->n; n++) {
+ /* Trigger the conversion */
+ outb(MPC624_ADSCK, dev->iobase + MPC624_ADC);
+ udelay(1);
+ outb(MPC624_ADCS | MPC624_ADSCK, dev->iobase + MPC624_ADC);
+ udelay(1);
+ outb(0, dev->iobase + MPC624_ADC);
+ udelay(1);
+
+ /* Wait for the conversion to end */
+ ret = comedi_timeout(dev, s, insn, mpc624_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Start reading data */
+ data_in = 0;
+ data_out = devpriv->ulConvertionRate;
+ udelay(1);
+ for (i = 0; i < 32; i++) {
+ /* Set the clock low */
+ outb(0, dev->iobase + MPC624_ADC);
+ udelay(1);
+
+ if (data_out & (1 << 31)) { /* the next bit is a 1 */
+ /* Set the ADSDI line (send to MPC624) */
+ outb(MPC624_ADSDI, dev->iobase + MPC624_ADC);
+ udelay(1);
+ /* Set the clock high */
+ outb(MPC624_ADSCK | MPC624_ADSDI,
+ dev->iobase + MPC624_ADC);
+ } else { /* the next bit is a 0 */
+
+ /* Set the ADSDI line (send to MPC624) */
+ outb(0, dev->iobase + MPC624_ADC);
+ udelay(1);
+ /* Set the clock high */
+ outb(MPC624_ADSCK, dev->iobase + MPC624_ADC);
+ }
+ /* Read ADSDO on high clock (receive from MPC624) */
+ udelay(1);
+ data_in <<= 1;
+ data_in |=
+ (inb(dev->iobase + MPC624_ADC) & MPC624_ADSDO) >> 4;
+ udelay(1);
+
+ data_out <<= 1;
+ }
+
+ /*
+ * Received 32-bit long value consist of:
+ * 31: EOC -
+ * (End Of Transmission) bit - should be 0
+ * 30: DMY
+ * (Dummy) bit - should be 0
+ * 29: SIG
+ * (Sign) bit- 1 if the voltage is positive,
+ * 0 if negative
+ * 28: MSB
+ * (Most Significant Bit) - the first bit of
+ * the conversion result
+ * ....
+ * 05: LSB
+ * (Least Significant Bit)- the last bit of the
+ * conversion result
+ * 04-00: sub-LSB
+ * - sub-LSBs are basically noise, but when
+ * averaged properly, they can increase conversion
+ * precision up to 29 bits; they can be discarded
+ * without loss of resolution.
+ */
+
+ if (data_in & MPC624_EOC_BIT)
+ dev_dbg(dev->class_dev,
+ "EOC bit is set (data_in=%lu)!", data_in);
+ if (data_in & MPC624_DMY_BIT)
+ dev_dbg(dev->class_dev,
+ "DMY bit is set (data_in=%lu)!", data_in);
+ if (data_in & MPC624_SGN_BIT) { /* Volatge is positive */
+ /*
+ * comedi operates on unsigned numbers, so mask off EOC
+ * and DMY and don't clear the SGN bit
+ */
+ data_in &= 0x3FFFFFFF;
+ data[n] = data_in;
+ } else { /* The voltage is negative */
+ /*
+ * data_in contains a number in 30-bit two's complement
+ * code and we must deal with it
+ */
+ data_in |= MPC624_SGN_BIT;
+ data_in = ~data_in;
+ data_in += 1;
+ data_in &= ~(MPC624_EOC_BIT | MPC624_DMY_BIT);
+ /* clear EOC and DMY bits */
+ data_in = 0x20000000 - data_in;
+ data[n] = data_in;
+ }
+ }
+
+ /* Return the number of samples read/written */
+ return n;
+}
+
+static int mpc624_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct mpc624_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ switch (it->options[1]) {
+ case 0:
+ devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz;
+ break;
+ case 1:
+ devpriv->ulConvertionRate = MPC624_SPEED_1_76_kHz;
+ break;
+ case 2:
+ devpriv->ulConvertionRate = MPC624_SPEED_880_Hz;
+ break;
+ case 3:
+ devpriv->ulConvertionRate = MPC624_SPEED_440_Hz;
+ break;
+ case 4:
+ devpriv->ulConvertionRate = MPC624_SPEED_220_Hz;
+ break;
+ case 5:
+ devpriv->ulConvertionRate = MPC624_SPEED_110_Hz;
+ break;
+ case 6:
+ devpriv->ulConvertionRate = MPC624_SPEED_55_Hz;
+ break;
+ case 7:
+ devpriv->ulConvertionRate = MPC624_SPEED_27_5_Hz;
+ break;
+ case 8:
+ devpriv->ulConvertionRate = MPC624_SPEED_13_75_Hz;
+ break;
+ case 9:
+ devpriv->ulConvertionRate = MPC624_SPEED_6_875_Hz;
+ break;
+ default:
+ devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = 8;
+ switch (it->options[1]) {
+ default:
+ s->maxdata = 0x3FFFFFFF;
+ }
+
+ switch (it->options[1]) {
+ case 0:
+ s->range_table = &range_mpc624_bipolar1;
+ break;
+ default:
+ s->range_table = &range_mpc624_bipolar10;
+ }
+ s->len_chanlist = 1;
+ s->insn_read = mpc624_ai_rinsn;
+
+ return 0;
+}
+
+static struct comedi_driver mpc624_driver = {
+ .driver_name = "mpc624",
+ .module = THIS_MODULE,
+ .attach = mpc624_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(mpc624_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/multiq3.c b/drivers/staging/comedi/drivers/multiq3.c
new file mode 100644
index 000000000..847121921
--- /dev/null
+++ b/drivers/staging/comedi/drivers/multiq3.c
@@ -0,0 +1,289 @@
+/*
+ comedi/drivers/multiq3.c
+ Hardware driver for Quanser Consulting MultiQ-3 board
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+/*
+Driver: multiq3
+Description: Quanser Consulting MultiQ-3
+Author: Anders Blomdell <anders.blomdell@control.lth.se>
+Status: works
+Devices: [Quanser Consulting] MultiQ-3 (multiq3)
+
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+/*
+ * MULTIQ-3 port offsets
+ */
+#define MULTIQ3_DIGIN_PORT 0
+#define MULTIQ3_DIGOUT_PORT 0
+#define MULTIQ3_DAC_DATA 2
+#define MULTIQ3_AD_DATA 4
+#define MULTIQ3_AD_CS 4
+#define MULTIQ3_STATUS 6
+#define MULTIQ3_CONTROL 6
+#define MULTIQ3_CLK_DATA 8
+#define MULTIQ3_ENC_DATA 12
+#define MULTIQ3_ENC_CONTROL 14
+
+/*
+ * flags for CONTROL register
+ */
+#define MULTIQ3_AD_MUX_EN 0x0040
+#define MULTIQ3_AD_AUTOZ 0x0080
+#define MULTIQ3_AD_AUTOCAL 0x0100
+#define MULTIQ3_AD_SH 0x0200
+#define MULTIQ3_AD_CLOCK_4M 0x0400
+#define MULTIQ3_DA_LOAD 0x1800
+
+#define MULTIQ3_CONTROL_MUST 0x0600
+
+/*
+ * flags for STATUS register
+ */
+#define MULTIQ3_STATUS_EOC 0x008
+#define MULTIQ3_STATUS_EOC_I 0x010
+
+/*
+ * flags for encoder control
+ */
+#define MULTIQ3_CLOCK_DATA 0x00
+#define MULTIQ3_CLOCK_SETUP 0x18
+#define MULTIQ3_INPUT_SETUP 0x41
+#define MULTIQ3_QUAD_X4 0x38
+#define MULTIQ3_BP_RESET 0x01
+#define MULTIQ3_CNTR_RESET 0x02
+#define MULTIQ3_TRSFRPR_CTR 0x08
+#define MULTIQ3_TRSFRCNTR_OL 0x10
+#define MULTIQ3_EFLAG_RESET 0x06
+
+#define MULTIQ3_TIMEOUT 30
+
+static int multiq3_ai_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + MULTIQ3_STATUS);
+ if (status & context)
+ return 0;
+ return -EBUSY;
+}
+
+static int multiq3_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int n;
+ int chan;
+ unsigned int hi, lo;
+ int ret;
+
+ chan = CR_CHAN(insn->chanspec);
+ outw(MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3),
+ dev->iobase + MULTIQ3_CONTROL);
+
+ ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
+ MULTIQ3_STATUS_EOC);
+ if (ret)
+ return ret;
+
+ for (n = 0; n < insn->n; n++) {
+ outw(0, dev->iobase + MULTIQ3_AD_CS);
+
+ ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
+ MULTIQ3_STATUS_EOC_I);
+ if (ret)
+ return ret;
+
+ hi = inb(dev->iobase + MULTIQ3_AD_CS);
+ lo = inb(dev->iobase + MULTIQ3_AD_CS);
+ data[n] = (((hi << 8) | lo) + 0x1000) & 0x1fff;
+ }
+
+ return n;
+}
+
+static int multiq3_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(MULTIQ3_CONTROL_MUST | MULTIQ3_DA_LOAD | chan,
+ dev->iobase + MULTIQ3_CONTROL);
+ outw(val, dev->iobase + MULTIQ3_DAC_DATA);
+ outw(MULTIQ3_CONTROL_MUST, dev->iobase + MULTIQ3_CONTROL);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int multiq3_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = inw(dev->iobase + MULTIQ3_DIGIN_PORT);
+
+ return insn->n;
+}
+
+static int multiq3_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + MULTIQ3_DIGOUT_PORT);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int multiq3_encoder_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int chan = CR_CHAN(insn->chanspec);
+ int control = MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3);
+ int value;
+ int n;
+
+ for (n = 0; n < insn->n; n++) {
+ outw(control, dev->iobase + MULTIQ3_CONTROL);
+ outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
+ outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CONTROL);
+ value = inb(dev->iobase + MULTIQ3_ENC_DATA);
+ value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 8);
+ value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 16);
+ data[n] = (value + 0x800000) & 0xffffff;
+ }
+
+ return n;
+}
+
+static void encoder_reset(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s = &dev->subdevices[4];
+ int chan;
+
+ for (chan = 0; chan < s->n_chan; chan++) {
+ int control =
+ MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3);
+ outw(control, dev->iobase + MULTIQ3_CONTROL);
+ outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
+ outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
+ outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA);
+ outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL);
+ outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL);
+ outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CONTROL);
+ outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
+ }
+}
+
+static int multiq3_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->insn_read = multiq3_ai_insn_read;
+ s->maxdata = 0x1fff;
+ s->range_table = &range_bipolar5;
+
+ s = &dev->subdevices[1];
+ /* ao subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 0xfff;
+ s->range_table = &range_bipolar5;
+ s->insn_write = multiq3_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ /* di subdevice */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->insn_bits = multiq3_di_insn_bits;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+
+ s = &dev->subdevices[3];
+ /* do subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->insn_bits = multiq3_do_insn_bits;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->state = 0;
+
+ s = &dev->subdevices[4];
+ /* encoder (counter) subdevice */
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = it->options[2] * 2;
+ s->insn_read = multiq3_encoder_insn_read;
+ s->maxdata = 0xffffff;
+ s->range_table = &range_unknown;
+
+ encoder_reset(dev);
+
+ return 0;
+}
+
+static struct comedi_driver multiq3_driver = {
+ .driver_name = "multiq3",
+ .module = THIS_MODULE,
+ .attach = multiq3_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(multiq3_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_6527.c b/drivers/staging/comedi/drivers/ni_6527.c
new file mode 100644
index 000000000..62a817e4c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_6527.c
@@ -0,0 +1,500 @@
+/*
+ * ni_6527.c
+ * Comedi driver for National Instruments PCI-6527
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_6527
+ * Description: National Instruments 6527
+ * Devices: [National Instruments] PCI-6527 (pci-6527), PXI-6527 (pxi-6527)
+ * Author: David A. Schleef <ds@schleef.org>
+ * Updated: Sat, 25 Jan 2003 13:24:40 -0800
+ * Status: works
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * PCI BAR1 - Register memory map
+ *
+ * Manuals (available from ftp://ftp.natinst.com/support/manuals)
+ * 370106b.pdf 6527 Register Level Programmer Manual
+ */
+#define NI6527_DI_REG(x) (0x00 + (x))
+#define NI6527_DO_REG(x) (0x03 + (x))
+#define NI6527_ID_REG 0x06
+#define NI6527_CLR_REG 0x07
+#define NI6527_CLR_EDGE (1 << 3)
+#define NI6527_CLR_OVERFLOW (1 << 2)
+#define NI6527_CLR_FILT (1 << 1)
+#define NI6527_CLR_INTERVAL (1 << 0)
+#define NI6527_CLR_IRQS (NI6527_CLR_EDGE | NI6527_CLR_OVERFLOW)
+#define NI6527_CLR_RESET_FILT (NI6527_CLR_FILT | NI6527_CLR_INTERVAL)
+#define NI6527_FILT_INTERVAL_REG(x) (0x08 + (x))
+#define NI6527_FILT_ENA_REG(x) (0x0c + (x))
+#define NI6527_STATUS_REG 0x14
+#define NI6527_STATUS_IRQ (1 << 2)
+#define NI6527_STATUS_OVERFLOW (1 << 1)
+#define NI6527_STATUS_EDGE (1 << 0)
+#define NI6527_CTRL_REG 0x15
+#define NI6527_CTRL_FALLING (1 << 4)
+#define NI6527_CTRL_RISING (1 << 3)
+#define NI6527_CTRL_IRQ (1 << 2)
+#define NI6527_CTRL_OVERFLOW (1 << 1)
+#define NI6527_CTRL_EDGE (1 << 0)
+#define NI6527_CTRL_DISABLE_IRQS 0
+#define NI6527_CTRL_ENABLE_IRQS (NI6527_CTRL_FALLING | \
+ NI6527_CTRL_RISING | \
+ NI6527_CTRL_IRQ | NI6527_CTRL_EDGE)
+#define NI6527_RISING_EDGE_REG(x) (0x18 + (x))
+#define NI6527_FALLING_EDGE_REG(x) (0x20 + (x))
+
+enum ni6527_boardid {
+ BOARD_PCI6527,
+ BOARD_PXI6527,
+};
+
+struct ni6527_board {
+ const char *name;
+};
+
+static const struct ni6527_board ni6527_boards[] = {
+ [BOARD_PCI6527] = {
+ .name = "pci-6527",
+ },
+ [BOARD_PXI6527] = {
+ .name = "pxi-6527",
+ },
+};
+
+struct ni6527_private {
+ unsigned int filter_interval;
+ unsigned int filter_enable;
+};
+
+static void ni6527_set_filter_interval(struct comedi_device *dev,
+ unsigned int val)
+{
+ struct ni6527_private *devpriv = dev->private;
+
+ if (val != devpriv->filter_interval) {
+ writeb(val & 0xff, dev->mmio + NI6527_FILT_INTERVAL_REG(0));
+ writeb((val >> 8) & 0xff,
+ dev->mmio + NI6527_FILT_INTERVAL_REG(1));
+ writeb((val >> 16) & 0x0f,
+ dev->mmio + NI6527_FILT_INTERVAL_REG(2));
+
+ writeb(NI6527_CLR_INTERVAL, dev->mmio + NI6527_CLR_REG);
+
+ devpriv->filter_interval = val;
+ }
+}
+
+static void ni6527_set_filter_enable(struct comedi_device *dev,
+ unsigned int val)
+{
+ writeb(val & 0xff, dev->mmio + NI6527_FILT_ENA_REG(0));
+ writeb((val >> 8) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(1));
+ writeb((val >> 16) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(2));
+}
+
+static int ni6527_di_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni6527_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int interval;
+
+ switch (data[0]) {
+ case INSN_CONFIG_FILTER:
+ /*
+ * The deglitch filter interval is specified in nanoseconds.
+ * The hardware supports intervals in 200ns increments. Round
+ * the user values up and return the actual interval.
+ */
+ interval = (data[1] + 100) / 200;
+ data[1] = interval * 200;
+
+ if (interval) {
+ ni6527_set_filter_interval(dev, interval);
+ devpriv->filter_enable |= 1 << chan;
+ } else {
+ devpriv->filter_enable &= ~(1 << chan);
+ }
+ ni6527_set_filter_enable(dev, devpriv->filter_enable);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int ni6527_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int val;
+
+ val = readb(dev->mmio + NI6527_DI_REG(0));
+ val |= (readb(dev->mmio + NI6527_DI_REG(1)) << 8);
+ val |= (readb(dev->mmio + NI6527_DI_REG(2)) << 16);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int ni6527_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ /* Outputs are inverted */
+ unsigned int val = s->state ^ 0xffffff;
+
+ if (mask & 0x0000ff)
+ writeb(val & 0xff, dev->mmio + NI6527_DO_REG(0));
+ if (mask & 0x00ff00)
+ writeb((val >> 8) & 0xff,
+ dev->mmio + NI6527_DO_REG(1));
+ if (mask & 0xff0000)
+ writeb((val >> 16) & 0xff,
+ dev->mmio + NI6527_DO_REG(2));
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static irqreturn_t ni6527_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+
+ status = readb(dev->mmio + NI6527_STATUS_REG);
+ if (!(status & NI6527_STATUS_IRQ))
+ return IRQ_NONE;
+
+ if (status & NI6527_STATUS_EDGE) {
+ comedi_buf_write_samples(s, &s->state, 1);
+ comedi_handle_events(dev, s);
+ }
+
+ writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int ni6527_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int ni6527_intr_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG);
+ writeb(NI6527_CTRL_ENABLE_IRQS, dev->mmio + NI6527_CTRL_REG);
+
+ return 0;
+}
+
+static int ni6527_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG);
+
+ return 0;
+}
+
+static int ni6527_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static void ni6527_set_edge_detection(struct comedi_device *dev,
+ unsigned int mask,
+ unsigned int rising,
+ unsigned int falling)
+{
+ unsigned int i;
+
+ rising &= mask;
+ falling &= mask;
+ for (i = 0; i < 2; i++) {
+ if (mask & 0xff) {
+ if (~mask & 0xff) {
+ /* preserve rising-edge detection channels */
+ rising |= readb(dev->mmio +
+ NI6527_RISING_EDGE_REG(i)) &
+ (~mask & 0xff);
+ /* preserve falling-edge detection channels */
+ falling |= readb(dev->mmio +
+ NI6527_FALLING_EDGE_REG(i)) &
+ (~mask & 0xff);
+ }
+ /* update rising-edge detection channels */
+ writeb(rising & 0xff,
+ dev->mmio + NI6527_RISING_EDGE_REG(i));
+ /* update falling-edge detection channels */
+ writeb(falling & 0xff,
+ dev->mmio + NI6527_FALLING_EDGE_REG(i));
+ }
+ rising >>= 8;
+ falling >>= 8;
+ mask >>= 8;
+ }
+}
+
+static int ni6527_intr_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask = 0xffffffff;
+ unsigned int rising, falling, shift;
+
+ switch (data[0]) {
+ case INSN_CONFIG_CHANGE_NOTIFY:
+ /* check_insn_config_length() does not check this instruction */
+ if (insn->n != 3)
+ return -EINVAL;
+ rising = data[1];
+ falling = data[2];
+ ni6527_set_edge_detection(dev, mask, rising, falling);
+ break;
+ case INSN_CONFIG_DIGITAL_TRIG:
+ /* check trigger number */
+ if (data[1] != 0)
+ return -EINVAL;
+ /* check digital trigger operation */
+ switch (data[2]) {
+ case COMEDI_DIGITAL_TRIG_DISABLE:
+ rising = 0;
+ falling = 0;
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
+ /* check shift amount */
+ shift = data[3];
+ if (shift >= s->n_chan) {
+ mask = 0;
+ rising = 0;
+ falling = 0;
+ } else {
+ mask <<= shift;
+ rising = data[4] << shift;
+ falling = data[5] << shift;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ ni6527_set_edge_detection(dev, mask, rising, falling);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static void ni6527_reset(struct comedi_device *dev)
+{
+ /* disable deglitch filters on all channels */
+ ni6527_set_filter_enable(dev, 0);
+
+ /* disable edge detection */
+ ni6527_set_edge_detection(dev, 0xffffffff, 0, 0);
+
+ writeb(NI6527_CLR_IRQS | NI6527_CLR_RESET_FILT,
+ dev->mmio + NI6527_CLR_REG);
+ writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG);
+}
+
+static int ni6527_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni6527_board *board = NULL;
+ struct ni6527_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(ni6527_boards))
+ board = &ni6527_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 1);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ /* make sure this is actually a 6527 device */
+ if (readb(dev->mmio + NI6527_ID_REG) != 0x27)
+ return -ENODEV;
+
+ ni6527_reset(dev);
+
+ ret = request_irq(pcidev->irq, ni6527_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = ni6527_di_insn_config;
+ s->insn_bits = ni6527_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni6527_do_insn_bits;
+
+ /* Edge detection interrupt subdevice */
+ s = &dev->subdevices[2];
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = ni6527_intr_insn_config;
+ s->insn_bits = ni6527_intr_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = ni6527_intr_cmdtest;
+ s->do_cmd = ni6527_intr_cmd;
+ s->cancel = ni6527_intr_cancel;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static void ni6527_detach(struct comedi_device *dev)
+{
+ if (dev->mmio)
+ ni6527_reset(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver ni6527_driver = {
+ .driver_name = "ni_6527",
+ .module = THIS_MODULE,
+ .auto_attach = ni6527_auto_attach,
+ .detach = ni6527_detach,
+};
+
+static int ni6527_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni6527_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni6527_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x2b10), BOARD_PXI6527 },
+ { PCI_VDEVICE(NI, 0x2b20), BOARD_PCI6527 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni6527_pci_table);
+
+static struct pci_driver ni6527_pci_driver = {
+ .name = "ni_6527",
+ .id_table = ni6527_pci_table,
+ .probe = ni6527_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni6527_driver, ni6527_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for National Instruments PCI-6527");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_65xx.c b/drivers/staging/comedi/drivers/ni_65xx.c
new file mode 100644
index 000000000..800d57426
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_65xx.c
@@ -0,0 +1,831 @@
+/*
+ * ni_65xx.c
+ * Comedi driver for National Instruments PCI-65xx static dio boards
+ *
+ * Copyright (C) 2006 Jon Grierson <jd@renko.co.uk>
+ * Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_65xx
+ * Description: National Instruments 65xx static dio boards
+ * Author: Jon Grierson <jd@renko.co.uk>,
+ * Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: testing
+ * Devices: [National Instruments] PCI-6509 (pci-6509), PXI-6509 (pxi-6509),
+ * PCI-6510 (pci-6510), PCI-6511 (pci-6511), PXI-6511 (pxi-6511),
+ * PCI-6512 (pci-6512), PXI-6512 (pxi-6512), PCI-6513 (pci-6513),
+ * PXI-6513 (pxi-6513), PCI-6514 (pci-6514), PXI-6514 (pxi-6514),
+ * PCI-6515 (pxi-6515), PXI-6515 (pxi-6515), PCI-6516 (pci-6516),
+ * PCI-6517 (pci-6517), PCI-6518 (pci-6518), PCI-6519 (pci-6519),
+ * PCI-6520 (pci-6520), PCI-6521 (pci-6521), PXI-6521 (pxi-6521),
+ * PCI-6528 (pci-6528), PXI-6528 (pxi-6528)
+ * Updated: Mon, 21 Jul 2014 12:49:58 +0000
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ *
+ * Based on the PCI-6527 driver by ds.
+ * The interrupt subdevice (subdevice 3) is probably broken for all
+ * boards except maybe the 6514.
+ *
+ * This driver previously inverted the outputs on PCI-6513 through to
+ * PCI-6519 and on PXI-6513 through to PXI-6515. It no longer inverts
+ * outputs on those cards by default as it didn't make much sense. If
+ * you require the outputs to be inverted on those cards for legacy
+ * reasons, set the module parameter "legacy_invert_outputs=true" when
+ * loading the module, or set "ni_65xx.legacy_invert_outputs=true" on
+ * the kernel command line if the driver is built in to the kernel.
+ */
+
+/*
+ * Manuals (available from ftp://ftp.natinst.com/support/manuals)
+ *
+ * 370106b.pdf 6514 Register Level Programmer Manual
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+/*
+ * PCI BAR1 Register Map
+ */
+
+/* Non-recurring Registers (8-bit except where noted) */
+#define NI_65XX_ID_REG 0x00
+#define NI_65XX_CLR_REG 0x01
+#define NI_65XX_CLR_WDOG_INT (1 << 6)
+#define NI_65XX_CLR_WDOG_PING (1 << 5)
+#define NI_65XX_CLR_WDOG_EXP (1 << 4)
+#define NI_65XX_CLR_EDGE_INT (1 << 3)
+#define NI_65XX_CLR_OVERFLOW_INT (1 << 2)
+#define NI_65XX_STATUS_REG 0x02
+#define NI_65XX_STATUS_WDOG_INT (1 << 5)
+#define NI_65XX_STATUS_FALL_EDGE (1 << 4)
+#define NI_65XX_STATUS_RISE_EDGE (1 << 3)
+#define NI_65XX_STATUS_INT (1 << 2)
+#define NI_65XX_STATUS_OVERFLOW_INT (1 << 1)
+#define NI_65XX_STATUS_EDGE_INT (1 << 0)
+#define NI_65XX_CTRL_REG 0x03
+#define NI_65XX_CTRL_WDOG_ENA (1 << 5)
+#define NI_65XX_CTRL_FALL_EDGE_ENA (1 << 4)
+#define NI_65XX_CTRL_RISE_EDGE_ENA (1 << 3)
+#define NI_65XX_CTRL_INT_ENA (1 << 2)
+#define NI_65XX_CTRL_OVERFLOW_ENA (1 << 1)
+#define NI_65XX_CTRL_EDGE_ENA (1 << 0)
+#define NI_65XX_REV_REG 0x04 /* 32-bit */
+#define NI_65XX_FILTER_REG 0x08 /* 32-bit */
+#define NI_65XX_RTSI_ROUTE_REG 0x0c /* 16-bit */
+#define NI_65XX_RTSI_EDGE_REG 0x0e /* 16-bit */
+#define NI_65XX_RTSI_WDOG_REG 0x10 /* 16-bit */
+#define NI_65XX_RTSI_TRIG_REG 0x12 /* 16-bit */
+#define NI_65XX_AUTO_CLK_SEL_REG 0x14 /* PXI-6528 only */
+#define NI_65XX_AUTO_CLK_SEL_STATUS (1 << 1)
+#define NI_65XX_AUTO_CLK_SEL_DISABLE (1 << 0)
+#define NI_65XX_WDOG_CTRL_REG 0x15
+#define NI_65XX_WDOG_CTRL_ENA (1 << 0)
+#define NI_65XX_RTSI_CFG_REG 0x16
+#define NI_65XX_RTSI_CFG_RISE_SENSE (1 << 2)
+#define NI_65XX_RTSI_CFG_FALL_SENSE (1 << 1)
+#define NI_65XX_RTSI_CFG_SYNC_DETECT (1 << 0)
+#define NI_65XX_WDOG_STATUS_REG 0x17
+#define NI_65XX_WDOG_STATUS_EXP (1 << 0)
+#define NI_65XX_WDOG_INTERVAL_REG 0x18 /* 32-bit */
+
+/* Recurring port registers (8-bit) */
+#define NI_65XX_PORT(x) ((x) * 0x10)
+#define NI_65XX_IO_DATA_REG(x) (0x40 + NI_65XX_PORT(x))
+#define NI_65XX_IO_SEL_REG(x) (0x41 + NI_65XX_PORT(x))
+#define NI_65XX_IO_SEL_OUTPUT (0 << 0)
+#define NI_65XX_IO_SEL_INPUT (1 << 0)
+#define NI_65XX_RISE_EDGE_ENA_REG(x) (0x42 + NI_65XX_PORT(x))
+#define NI_65XX_FALL_EDGE_ENA_REG(x) (0x43 + NI_65XX_PORT(x))
+#define NI_65XX_FILTER_ENA(x) (0x44 + NI_65XX_PORT(x))
+#define NI_65XX_WDOG_HIZ_REG(x) (0x46 + NI_65XX_PORT(x))
+#define NI_65XX_WDOG_ENA(x) (0x47 + NI_65XX_PORT(x))
+#define NI_65XX_WDOG_HI_LO_REG(x) (0x48 + NI_65XX_PORT(x))
+#define NI_65XX_RTSI_ENA(x) (0x49 + NI_65XX_PORT(x))
+
+#define NI_65XX_PORT_TO_CHAN(x) ((x) * 8)
+#define NI_65XX_CHAN_TO_PORT(x) ((x) / 8)
+#define NI_65XX_CHAN_TO_MASK(x) (1 << ((x) % 8))
+
+enum ni_65xx_boardid {
+ BOARD_PCI6509,
+ BOARD_PXI6509,
+ BOARD_PCI6510,
+ BOARD_PCI6511,
+ BOARD_PXI6511,
+ BOARD_PCI6512,
+ BOARD_PXI6512,
+ BOARD_PCI6513,
+ BOARD_PXI6513,
+ BOARD_PCI6514,
+ BOARD_PXI6514,
+ BOARD_PCI6515,
+ BOARD_PXI6515,
+ BOARD_PCI6516,
+ BOARD_PCI6517,
+ BOARD_PCI6518,
+ BOARD_PCI6519,
+ BOARD_PCI6520,
+ BOARD_PCI6521,
+ BOARD_PXI6521,
+ BOARD_PCI6528,
+ BOARD_PXI6528,
+};
+
+struct ni_65xx_board {
+ const char *name;
+ unsigned num_dio_ports;
+ unsigned num_di_ports;
+ unsigned num_do_ports;
+ unsigned legacy_invert:1;
+};
+
+static const struct ni_65xx_board ni_65xx_boards[] = {
+ [BOARD_PCI6509] = {
+ .name = "pci-6509",
+ .num_dio_ports = 12,
+ },
+ [BOARD_PXI6509] = {
+ .name = "pxi-6509",
+ .num_dio_ports = 12,
+ },
+ [BOARD_PCI6510] = {
+ .name = "pci-6510",
+ .num_di_ports = 4,
+ },
+ [BOARD_PCI6511] = {
+ .name = "pci-6511",
+ .num_di_ports = 8,
+ },
+ [BOARD_PXI6511] = {
+ .name = "pxi-6511",
+ .num_di_ports = 8,
+ },
+ [BOARD_PCI6512] = {
+ .name = "pci-6512",
+ .num_do_ports = 8,
+ },
+ [BOARD_PXI6512] = {
+ .name = "pxi-6512",
+ .num_do_ports = 8,
+ },
+ [BOARD_PCI6513] = {
+ .name = "pci-6513",
+ .num_do_ports = 8,
+ .legacy_invert = 1,
+ },
+ [BOARD_PXI6513] = {
+ .name = "pxi-6513",
+ .num_do_ports = 8,
+ .legacy_invert = 1,
+ },
+ [BOARD_PCI6514] = {
+ .name = "pci-6514",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .legacy_invert = 1,
+ },
+ [BOARD_PXI6514] = {
+ .name = "pxi-6514",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .legacy_invert = 1,
+ },
+ [BOARD_PCI6515] = {
+ .name = "pci-6515",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .legacy_invert = 1,
+ },
+ [BOARD_PXI6515] = {
+ .name = "pxi-6515",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .legacy_invert = 1,
+ },
+ [BOARD_PCI6516] = {
+ .name = "pci-6516",
+ .num_do_ports = 4,
+ .legacy_invert = 1,
+ },
+ [BOARD_PCI6517] = {
+ .name = "pci-6517",
+ .num_do_ports = 4,
+ .legacy_invert = 1,
+ },
+ [BOARD_PCI6518] = {
+ .name = "pci-6518",
+ .num_di_ports = 2,
+ .num_do_ports = 2,
+ .legacy_invert = 1,
+ },
+ [BOARD_PCI6519] = {
+ .name = "pci-6519",
+ .num_di_ports = 2,
+ .num_do_ports = 2,
+ .legacy_invert = 1,
+ },
+ [BOARD_PCI6520] = {
+ .name = "pci-6520",
+ .num_di_ports = 1,
+ .num_do_ports = 1,
+ },
+ [BOARD_PCI6521] = {
+ .name = "pci-6521",
+ .num_di_ports = 1,
+ .num_do_ports = 1,
+ },
+ [BOARD_PXI6521] = {
+ .name = "pxi-6521",
+ .num_di_ports = 1,
+ .num_do_ports = 1,
+ },
+ [BOARD_PCI6528] = {
+ .name = "pci-6528",
+ .num_di_ports = 3,
+ .num_do_ports = 3,
+ },
+ [BOARD_PXI6528] = {
+ .name = "pxi-6528",
+ .num_di_ports = 3,
+ .num_do_ports = 3,
+ },
+};
+
+static bool ni_65xx_legacy_invert_outputs;
+module_param_named(legacy_invert_outputs, ni_65xx_legacy_invert_outputs,
+ bool, 0444);
+MODULE_PARM_DESC(legacy_invert_outputs,
+ "invert outputs of PCI/PXI-6513/6514/6515/6516/6517/6518/6519 for compatibility with old user code");
+
+static unsigned int ni_65xx_num_ports(struct comedi_device *dev)
+{
+ const struct ni_65xx_board *board = dev->board_ptr;
+
+ return board->num_dio_ports + board->num_di_ports + board->num_do_ports;
+}
+
+static void ni_65xx_disable_input_filters(struct comedi_device *dev)
+{
+ unsigned int num_ports = ni_65xx_num_ports(dev);
+ int i;
+
+ /* disable input filtering on all ports */
+ for (i = 0; i < num_ports; ++i)
+ writeb(0x00, dev->mmio + NI_65XX_FILTER_ENA(i));
+
+ /* set filter interval to 0 (32bit reg) */
+ writel(0x00000000, dev->mmio + NI_65XX_FILTER_REG);
+}
+
+/* updates edge detection for base_chan to base_chan+31 */
+static void ni_65xx_update_edge_detection(struct comedi_device *dev,
+ unsigned int base_chan,
+ unsigned int rising,
+ unsigned int falling)
+{
+ unsigned int num_ports = ni_65xx_num_ports(dev);
+ unsigned int port;
+
+ if (base_chan >= NI_65XX_PORT_TO_CHAN(num_ports))
+ return;
+
+ for (port = NI_65XX_CHAN_TO_PORT(base_chan); port < num_ports; port++) {
+ int bitshift = (int)(NI_65XX_PORT_TO_CHAN(port) - base_chan);
+ unsigned int port_mask, port_rising, port_falling;
+
+ if (bitshift >= 32)
+ break;
+
+ if (bitshift >= 0) {
+ port_mask = ~0U >> bitshift;
+ port_rising = rising >> bitshift;
+ port_falling = falling >> bitshift;
+ } else {
+ port_mask = ~0U << -bitshift;
+ port_rising = rising << -bitshift;
+ port_falling = falling << -bitshift;
+ }
+ if (port_mask & 0xff) {
+ if (~port_mask & 0xff) {
+ port_rising |=
+ readb(dev->mmio +
+ NI_65XX_RISE_EDGE_ENA_REG(port)) &
+ ~port_mask;
+ port_falling |=
+ readb(dev->mmio +
+ NI_65XX_FALL_EDGE_ENA_REG(port)) &
+ ~port_mask;
+ }
+ writeb(port_rising & 0xff,
+ dev->mmio + NI_65XX_RISE_EDGE_ENA_REG(port));
+ writeb(port_falling & 0xff,
+ dev->mmio + NI_65XX_FALL_EDGE_ENA_REG(port));
+ }
+ }
+}
+
+static void ni_65xx_disable_edge_detection(struct comedi_device *dev)
+{
+ /* clear edge detection for channels 0 to 31 */
+ ni_65xx_update_edge_detection(dev, 0, 0, 0);
+ /* clear edge detection for channels 32 to 63 */
+ ni_65xx_update_edge_detection(dev, 32, 0, 0);
+ /* clear edge detection for channels 64 to 95 */
+ ni_65xx_update_edge_detection(dev, 64, 0, 0);
+}
+
+static int ni_65xx_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long base_port = (unsigned long)s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int chan_mask = NI_65XX_CHAN_TO_MASK(chan);
+ unsigned port = base_port + NI_65XX_CHAN_TO_PORT(chan);
+ unsigned int interval;
+ unsigned int val;
+
+ switch (data[0]) {
+ case INSN_CONFIG_FILTER:
+ /*
+ * The deglitch filter interval is specified in nanoseconds.
+ * The hardware supports intervals in 200ns increments. Round
+ * the user values up and return the actual interval.
+ */
+ interval = (data[1] + 100) / 200;
+ if (interval > 0xfffff)
+ interval = 0xfffff;
+ data[1] = interval * 200;
+
+ /*
+ * Enable/disable the channel for deglitch filtering. Note
+ * that the filter interval is never set to '0'. This is done
+ * because other channels might still be enabled for filtering.
+ */
+ val = readb(dev->mmio + NI_65XX_FILTER_ENA(port));
+ if (interval) {
+ writel(interval, dev->mmio + NI_65XX_FILTER_REG);
+ val |= chan_mask;
+ } else {
+ val &= ~chan_mask;
+ }
+ writeb(val, dev->mmio + NI_65XX_FILTER_ENA(port));
+ break;
+
+ case INSN_CONFIG_DIO_OUTPUT:
+ if (s->type != COMEDI_SUBD_DIO)
+ return -EINVAL;
+ writeb(NI_65XX_IO_SEL_OUTPUT,
+ dev->mmio + NI_65XX_IO_SEL_REG(port));
+ break;
+
+ case INSN_CONFIG_DIO_INPUT:
+ if (s->type != COMEDI_SUBD_DIO)
+ return -EINVAL;
+ writeb(NI_65XX_IO_SEL_INPUT,
+ dev->mmio + NI_65XX_IO_SEL_REG(port));
+ break;
+
+ case INSN_CONFIG_DIO_QUERY:
+ if (s->type != COMEDI_SUBD_DIO)
+ return -EINVAL;
+ val = readb(dev->mmio + NI_65XX_IO_SEL_REG(port));
+ data[1] = (val == NI_65XX_IO_SEL_INPUT) ? COMEDI_INPUT
+ : COMEDI_OUTPUT;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int ni_65xx_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long base_port = (unsigned long)s->private;
+ unsigned int base_chan = CR_CHAN(insn->chanspec);
+ int last_port_offset = NI_65XX_CHAN_TO_PORT(s->n_chan - 1);
+ unsigned read_bits = 0;
+ int port_offset;
+
+ for (port_offset = NI_65XX_CHAN_TO_PORT(base_chan);
+ port_offset <= last_port_offset; port_offset++) {
+ unsigned port = base_port + port_offset;
+ int base_port_channel = NI_65XX_PORT_TO_CHAN(port_offset);
+ unsigned port_mask, port_data, bits;
+ int bitshift = base_port_channel - base_chan;
+
+ if (bitshift >= 32)
+ break;
+ port_mask = data[0];
+ port_data = data[1];
+ if (bitshift > 0) {
+ port_mask >>= bitshift;
+ port_data >>= bitshift;
+ } else {
+ port_mask <<= -bitshift;
+ port_data <<= -bitshift;
+ }
+ port_mask &= 0xff;
+ port_data &= 0xff;
+
+ /* update the outputs */
+ if (port_mask) {
+ bits = readb(dev->mmio + NI_65XX_IO_DATA_REG(port));
+ bits ^= s->io_bits; /* invert if necessary */
+ bits &= ~port_mask;
+ bits |= (port_data & port_mask);
+ bits ^= s->io_bits; /* invert back */
+ writeb(bits, dev->mmio + NI_65XX_IO_DATA_REG(port));
+ }
+
+ /* read back the actual state */
+ bits = readb(dev->mmio + NI_65XX_IO_DATA_REG(port));
+ bits ^= s->io_bits; /* invert if necessary */
+ if (bitshift > 0)
+ bits <<= bitshift;
+ else
+ bits >>= -bitshift;
+
+ read_bits |= bits;
+ }
+ data[1] = read_bits;
+ return insn->n;
+}
+
+static irqreturn_t ni_65xx_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+
+ status = readb(dev->mmio + NI_65XX_STATUS_REG);
+ if ((status & NI_65XX_STATUS_INT) == 0)
+ return IRQ_NONE;
+ if ((status & NI_65XX_STATUS_EDGE_INT) == 0)
+ return IRQ_NONE;
+
+ writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT,
+ dev->mmio + NI_65XX_CLR_REG);
+
+ comedi_buf_write_samples(s, &s->state, 1);
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int ni_65xx_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int ni_65xx_intr_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT,
+ dev->mmio + NI_65XX_CLR_REG);
+ writeb(NI_65XX_CTRL_FALL_EDGE_ENA | NI_65XX_CTRL_RISE_EDGE_ENA |
+ NI_65XX_CTRL_INT_ENA | NI_65XX_CTRL_EDGE_ENA,
+ dev->mmio + NI_65XX_CTRL_REG);
+
+ return 0;
+}
+
+static int ni_65xx_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ writeb(0x00, dev->mmio + NI_65XX_CTRL_REG);
+
+ return 0;
+}
+
+static int ni_65xx_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static int ni_65xx_intr_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ switch (data[0]) {
+ case INSN_CONFIG_CHANGE_NOTIFY:
+ /* add instruction to check_insn_config_length() */
+ if (insn->n != 3)
+ return -EINVAL;
+
+ /* update edge detection for channels 0 to 31 */
+ ni_65xx_update_edge_detection(dev, 0, data[1], data[2]);
+ /* clear edge detection for channels 32 to 63 */
+ ni_65xx_update_edge_detection(dev, 32, 0, 0);
+ /* clear edge detection for channels 64 to 95 */
+ ni_65xx_update_edge_detection(dev, 64, 0, 0);
+ break;
+ case INSN_CONFIG_DIGITAL_TRIG:
+ /* check trigger number */
+ if (data[1] != 0)
+ return -EINVAL;
+ /* check digital trigger operation */
+ switch (data[2]) {
+ case COMEDI_DIGITAL_TRIG_DISABLE:
+ ni_65xx_disable_edge_detection(dev);
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
+ /*
+ * update edge detection for channels data[3]
+ * to (data[3] + 31)
+ */
+ ni_65xx_update_edge_detection(dev, data[3],
+ data[4], data[5]);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+/* ripped from mite.h and mite_setup2() to avoid mite dependency */
+#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */
+#define WENAB (1 << 7) /* window enable */
+
+static int ni_65xx_mite_init(struct pci_dev *pcidev)
+{
+ void __iomem *mite_base;
+ u32 main_phys_addr;
+
+ /* ioremap the MITE registers (BAR 0) temporarily */
+ mite_base = pci_ioremap_bar(pcidev, 0);
+ if (!mite_base)
+ return -ENOMEM;
+
+ /* set data window to main registers (BAR 1) */
+ main_phys_addr = pci_resource_start(pcidev, 1);
+ writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR);
+
+ /* finished with MITE registers */
+ iounmap(mite_base);
+ return 0;
+}
+
+static int ni_65xx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_65xx_board *board = NULL;
+ struct comedi_subdevice *s;
+ unsigned i;
+ int ret;
+
+ if (context < ARRAY_SIZE(ni_65xx_boards))
+ board = &ni_65xx_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = ni_65xx_mite_init(pcidev);
+ if (ret)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 1);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT,
+ dev->mmio + NI_65XX_CLR_REG);
+ writeb(0x00, dev->mmio + NI_65XX_CTRL_REG);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, ni_65xx_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ dev_info(dev->class_dev, "board: %s, ID=0x%02x", dev->board_name,
+ readb(dev->mmio + NI_65XX_ID_REG));
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ if (board->num_di_ports) {
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_di_ports);
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni_65xx_dio_insn_bits;
+ s->insn_config = ni_65xx_dio_insn_config;
+
+ /* the input ports always start at port 0 */
+ s->private = (void *)0;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[1];
+ if (board->num_do_ports) {
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_do_ports);
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni_65xx_dio_insn_bits;
+
+ /* the output ports always start after the input ports */
+ s->private = (void *)(unsigned long)board->num_di_ports;
+
+ /*
+ * Use the io_bits to handle the inverted outputs. Inverted
+ * outputs are only supported if the "legacy_invert_outputs"
+ * module parameter is set to "true".
+ */
+ if (ni_65xx_legacy_invert_outputs && board->legacy_invert)
+ s->io_bits = 0xff;
+
+ /* reset all output ports to comedi '0' */
+ for (i = 0; i < board->num_do_ports; ++i) {
+ writeb(s->io_bits, /* inverted if necessary */
+ dev->mmio +
+ NI_65XX_IO_DATA_REG(board->num_di_ports + i));
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ if (board->num_dio_ports) {
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_dio_ports);
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni_65xx_dio_insn_bits;
+ s->insn_config = ni_65xx_dio_insn_config;
+
+ /* the input/output ports always start at port 0 */
+ s->private = (void *)0;
+
+ /* configure all ports for input */
+ for (i = 0; i < board->num_dio_ports; ++i) {
+ writeb(NI_65XX_IO_SEL_INPUT,
+ dev->mmio + NI_65XX_IO_SEL_REG(i));
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni_65xx_intr_insn_bits;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->insn_config = ni_65xx_intr_insn_config;
+ s->do_cmdtest = ni_65xx_intr_cmdtest;
+ s->do_cmd = ni_65xx_intr_cmd;
+ s->cancel = ni_65xx_intr_cancel;
+ }
+
+ ni_65xx_disable_input_filters(dev);
+ ni_65xx_disable_edge_detection(dev);
+
+ return 0;
+}
+
+static void ni_65xx_detach(struct comedi_device *dev)
+{
+ if (dev->mmio)
+ writeb(0x00, dev->mmio + NI_65XX_CTRL_REG);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver ni_65xx_driver = {
+ .driver_name = "ni_65xx",
+ .module = THIS_MODULE,
+ .auto_attach = ni_65xx_auto_attach,
+ .detach = ni_65xx_detach,
+};
+
+static int ni_65xx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_65xx_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_65xx_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1710), BOARD_PXI6509 },
+ { PCI_VDEVICE(NI, 0x7085), BOARD_PCI6509 },
+ { PCI_VDEVICE(NI, 0x7086), BOARD_PXI6528 },
+ { PCI_VDEVICE(NI, 0x7087), BOARD_PCI6515 },
+ { PCI_VDEVICE(NI, 0x7088), BOARD_PCI6514 },
+ { PCI_VDEVICE(NI, 0x70a9), BOARD_PCI6528 },
+ { PCI_VDEVICE(NI, 0x70c3), BOARD_PCI6511 },
+ { PCI_VDEVICE(NI, 0x70c8), BOARD_PCI6513 },
+ { PCI_VDEVICE(NI, 0x70c9), BOARD_PXI6515 },
+ { PCI_VDEVICE(NI, 0x70cc), BOARD_PCI6512 },
+ { PCI_VDEVICE(NI, 0x70cd), BOARD_PXI6514 },
+ { PCI_VDEVICE(NI, 0x70d1), BOARD_PXI6513 },
+ { PCI_VDEVICE(NI, 0x70d2), BOARD_PXI6512 },
+ { PCI_VDEVICE(NI, 0x70d3), BOARD_PXI6511 },
+ { PCI_VDEVICE(NI, 0x7124), BOARD_PCI6510 },
+ { PCI_VDEVICE(NI, 0x7125), BOARD_PCI6516 },
+ { PCI_VDEVICE(NI, 0x7126), BOARD_PCI6517 },
+ { PCI_VDEVICE(NI, 0x7127), BOARD_PCI6518 },
+ { PCI_VDEVICE(NI, 0x7128), BOARD_PCI6519 },
+ { PCI_VDEVICE(NI, 0x718b), BOARD_PCI6521 },
+ { PCI_VDEVICE(NI, 0x718c), BOARD_PXI6521 },
+ { PCI_VDEVICE(NI, 0x71c5), BOARD_PCI6520 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_65xx_pci_table);
+
+static struct pci_driver ni_65xx_pci_driver = {
+ .name = "ni_65xx",
+ .id_table = ni_65xx_pci_table,
+ .probe = ni_65xx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_65xx_driver, ni_65xx_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for NI PCI-65xx static dio boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_660x.c b/drivers/staging/comedi/drivers/ni_660x.c
new file mode 100644
index 000000000..46647c64f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_660x.c
@@ -0,0 +1,1222 @@
+/*
+ comedi/drivers/ni_660x.c
+ Hardware driver for NI 660x devices
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+ * Driver: ni_660x
+ * Description: National Instruments 660x counter/timer boards
+ * Devices: [National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602,
+ * PXI-6608, PXI-6624
+ * Author: J.P. Mellor <jpmellor@rose-hulman.edu>,
+ * Herman.Bruyninckx@mech.kuleuven.ac.be,
+ * Wim.Meeussen@mech.kuleuven.ac.be,
+ * Klaas.Gadeyne@mech.kuleuven.ac.be,
+ * Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Updated: Fri, 15 Mar 2013 10:47:56 +0000
+ * Status: experimental
+ *
+ * Encoders work. PulseGeneration (both single pulse and pulse train)
+ * works. Buffered commands work for input but not output.
+ *
+ * References:
+ * DAQ 660x Register-Level Programmer Manual (NI 370505A-01)
+ * DAQ 6601/6602 User Manual (NI 322137B-01)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "mite.h"
+#include "ni_tio.h"
+
+enum ni_660x_constants {
+ min_counter_pfi_chan = 8,
+ max_dio_pfi_chan = 31,
+ counters_per_chip = 4
+};
+
+#define NUM_PFI_CHANNELS 40
+/* really there are only up to 3 dma channels, but the register layout allows
+for 4 */
+#define MAX_DMA_CHANNEL 4
+
+/* See Register-Level Programmer Manual page 3.1 */
+enum ni_660x_register {
+ NI660X_G0_INT_ACK,
+ NI660X_G0_STATUS,
+ NI660X_G1_INT_ACK,
+ NI660X_G1_STATUS,
+ NI660X_G01_STATUS,
+ NI660X_G0_CMD,
+ NI660X_STC_DIO_PARALLEL_INPUT,
+ NI660X_G1_CMD,
+ NI660X_G0_HW_SAVE,
+ NI660X_G1_HW_SAVE,
+ NI660X_STC_DIO_OUTPUT,
+ NI660X_STC_DIO_CONTROL,
+ NI660X_G0_SW_SAVE,
+ NI660X_G1_SW_SAVE,
+ NI660X_G0_MODE,
+ NI660X_G01_STATUS1,
+ NI660X_G1_MODE,
+ NI660X_STC_DIO_SERIAL_INPUT,
+ NI660X_G0_LOADA,
+ NI660X_G01_STATUS2,
+ NI660X_G0_LOADB,
+ NI660X_G1_LOADA,
+ NI660X_G1_LOADB,
+ NI660X_G0_INPUT_SEL,
+ NI660X_G1_INPUT_SEL,
+ NI660X_G0_AUTO_INC,
+ NI660X_G1_AUTO_INC,
+ NI660X_G01_RESET,
+ NI660X_G0_INT_ENA,
+ NI660X_G1_INT_ENA,
+ NI660X_G0_CNT_MODE,
+ NI660X_G1_CNT_MODE,
+ NI660X_G0_GATE2,
+ NI660X_G1_GATE2,
+ NI660X_G0_DMA_CFG,
+ NI660X_G0_DMA_STATUS,
+ NI660X_G1_DMA_CFG,
+ NI660X_G1_DMA_STATUS,
+ NI660X_G2_INT_ACK,
+ NI660X_G2_STATUS,
+ NI660X_G3_INT_ACK,
+ NI660X_G3_STATUS,
+ NI660X_G23_STATUS,
+ NI660X_G2_CMD,
+ NI660X_G3_CMD,
+ NI660X_G2_HW_SAVE,
+ NI660X_G3_HW_SAVE,
+ NI660X_G2_SW_SAVE,
+ NI660X_G3_SW_SAVE,
+ NI660X_G2_MODE,
+ NI660X_G23_STATUS1,
+ NI660X_G3_MODE,
+ NI660X_G2_LOADA,
+ NI660X_G23_STATUS2,
+ NI660X_G2_LOADB,
+ NI660X_G3_LOADA,
+ NI660X_G3_LOADB,
+ NI660X_G2_INPUT_SEL,
+ NI660X_G3_INPUT_SEL,
+ NI660X_G2_AUTO_INC,
+ NI660X_G3_AUTO_INC,
+ NI660X_G23_RESET,
+ NI660X_G2_INT_ENA,
+ NI660X_G3_INT_ENA,
+ NI660X_G2_CNT_MODE,
+ NI660X_G3_CNT_MODE,
+ NI660X_G3_GATE2,
+ NI660X_G2_GATE2,
+ NI660X_G2_DMA_CFG,
+ NI660X_G2_DMA_STATUS,
+ NI660X_G3_DMA_CFG,
+ NI660X_G3_DMA_STATUS,
+ NI660X_DIO32_INPUT,
+ NI660X_DIO32_OUTPUT,
+ NI660X_CLK_CFG,
+ NI660X_GLOBAL_INT_STATUS,
+ NI660X_DMA_CFG,
+ NI660X_GLOBAL_INT_CFG,
+ NI660X_IO_CFG_0_1,
+ NI660X_IO_CFG_2_3,
+ NI660X_IO_CFG_4_5,
+ NI660X_IO_CFG_6_7,
+ NI660X_IO_CFG_8_9,
+ NI660X_IO_CFG_10_11,
+ NI660X_IO_CFG_12_13,
+ NI660X_IO_CFG_14_15,
+ NI660X_IO_CFG_16_17,
+ NI660X_IO_CFG_18_19,
+ NI660X_IO_CFG_20_21,
+ NI660X_IO_CFG_22_23,
+ NI660X_IO_CFG_24_25,
+ NI660X_IO_CFG_26_27,
+ NI660X_IO_CFG_28_29,
+ NI660X_IO_CFG_30_31,
+ NI660X_IO_CFG_32_33,
+ NI660X_IO_CFG_34_35,
+ NI660X_IO_CFG_36_37,
+ NI660X_IO_CFG_38_39,
+ NI660X_NUM_REGS,
+};
+
+static inline unsigned IOConfigReg(unsigned pfi_channel)
+{
+ unsigned reg = NI660X_IO_CFG_0_1 + pfi_channel / 2;
+
+ BUG_ON(reg > NI660X_IO_CFG_38_39);
+ return reg;
+}
+
+enum ni_660x_register_width {
+ DATA_1B,
+ DATA_2B,
+ DATA_4B
+};
+
+enum ni_660x_register_direction {
+ NI_660x_READ,
+ NI_660x_WRITE,
+ NI_660x_READ_WRITE
+};
+
+enum ni_660x_pfi_output_select {
+ pfi_output_select_high_Z = 0,
+ pfi_output_select_counter = 1,
+ pfi_output_select_do = 2,
+ num_pfi_output_selects
+};
+
+enum ni_660x_subdevices {
+ NI_660X_DIO_SUBDEV = 1,
+ NI_660X_GPCT_SUBDEV_0 = 2
+};
+static inline unsigned NI_660X_GPCT_SUBDEV(unsigned index)
+{
+ return NI_660X_GPCT_SUBDEV_0 + index;
+}
+
+struct NI_660xRegisterData {
+ const char *name; /* Register Name */
+ int offset; /* Offset from base address from GPCT chip */
+ enum ni_660x_register_direction direction;
+ enum ni_660x_register_width size; /* 1 byte, 2 bytes, or 4 bytes */
+};
+
+static const struct NI_660xRegisterData registerData[NI660X_NUM_REGS] = {
+ {"G0 Interrupt Acknowledge", 0x004, NI_660x_WRITE, DATA_2B},
+ {"G0 Status Register", 0x004, NI_660x_READ, DATA_2B},
+ {"G1 Interrupt Acknowledge", 0x006, NI_660x_WRITE, DATA_2B},
+ {"G1 Status Register", 0x006, NI_660x_READ, DATA_2B},
+ {"G01 Status Register ", 0x008, NI_660x_READ, DATA_2B},
+ {"G0 Command Register", 0x00C, NI_660x_WRITE, DATA_2B},
+ {"STC DIO Parallel Input", 0x00E, NI_660x_READ, DATA_2B},
+ {"G1 Command Register", 0x00E, NI_660x_WRITE, DATA_2B},
+ {"G0 HW Save Register", 0x010, NI_660x_READ, DATA_4B},
+ {"G1 HW Save Register", 0x014, NI_660x_READ, DATA_4B},
+ {"STC DIO Output", 0x014, NI_660x_WRITE, DATA_2B},
+ {"STC DIO Control", 0x016, NI_660x_WRITE, DATA_2B},
+ {"G0 SW Save Register", 0x018, NI_660x_READ, DATA_4B},
+ {"G1 SW Save Register", 0x01C, NI_660x_READ, DATA_4B},
+ {"G0 Mode Register", 0x034, NI_660x_WRITE, DATA_2B},
+ {"G01 Joint Status 1 Register", 0x036, NI_660x_READ, DATA_2B},
+ {"G1 Mode Register", 0x036, NI_660x_WRITE, DATA_2B},
+ {"STC DIO Serial Input", 0x038, NI_660x_READ, DATA_2B},
+ {"G0 Load A Register", 0x038, NI_660x_WRITE, DATA_4B},
+ {"G01 Joint Status 2 Register", 0x03A, NI_660x_READ, DATA_2B},
+ {"G0 Load B Register", 0x03C, NI_660x_WRITE, DATA_4B},
+ {"G1 Load A Register", 0x040, NI_660x_WRITE, DATA_4B},
+ {"G1 Load B Register", 0x044, NI_660x_WRITE, DATA_4B},
+ {"G0 Input Select Register", 0x048, NI_660x_WRITE, DATA_2B},
+ {"G1 Input Select Register", 0x04A, NI_660x_WRITE, DATA_2B},
+ {"G0 Autoincrement Register", 0x088, NI_660x_WRITE, DATA_2B},
+ {"G1 Autoincrement Register", 0x08A, NI_660x_WRITE, DATA_2B},
+ {"G01 Joint Reset Register", 0x090, NI_660x_WRITE, DATA_2B},
+ {"G0 Interrupt Enable", 0x092, NI_660x_WRITE, DATA_2B},
+ {"G1 Interrupt Enable", 0x096, NI_660x_WRITE, DATA_2B},
+ {"G0 Counting Mode Register", 0x0B0, NI_660x_WRITE, DATA_2B},
+ {"G1 Counting Mode Register", 0x0B2, NI_660x_WRITE, DATA_2B},
+ {"G0 Second Gate Register", 0x0B4, NI_660x_WRITE, DATA_2B},
+ {"G1 Second Gate Register", 0x0B6, NI_660x_WRITE, DATA_2B},
+ {"G0 DMA Config Register", 0x0B8, NI_660x_WRITE, DATA_2B},
+ {"G0 DMA Status Register", 0x0B8, NI_660x_READ, DATA_2B},
+ {"G1 DMA Config Register", 0x0BA, NI_660x_WRITE, DATA_2B},
+ {"G1 DMA Status Register", 0x0BA, NI_660x_READ, DATA_2B},
+ {"G2 Interrupt Acknowledge", 0x104, NI_660x_WRITE, DATA_2B},
+ {"G2 Status Register", 0x104, NI_660x_READ, DATA_2B},
+ {"G3 Interrupt Acknowledge", 0x106, NI_660x_WRITE, DATA_2B},
+ {"G3 Status Register", 0x106, NI_660x_READ, DATA_2B},
+ {"G23 Status Register", 0x108, NI_660x_READ, DATA_2B},
+ {"G2 Command Register", 0x10C, NI_660x_WRITE, DATA_2B},
+ {"G3 Command Register", 0x10E, NI_660x_WRITE, DATA_2B},
+ {"G2 HW Save Register", 0x110, NI_660x_READ, DATA_4B},
+ {"G3 HW Save Register", 0x114, NI_660x_READ, DATA_4B},
+ {"G2 SW Save Register", 0x118, NI_660x_READ, DATA_4B},
+ {"G3 SW Save Register", 0x11C, NI_660x_READ, DATA_4B},
+ {"G2 Mode Register", 0x134, NI_660x_WRITE, DATA_2B},
+ {"G23 Joint Status 1 Register", 0x136, NI_660x_READ, DATA_2B},
+ {"G3 Mode Register", 0x136, NI_660x_WRITE, DATA_2B},
+ {"G2 Load A Register", 0x138, NI_660x_WRITE, DATA_4B},
+ {"G23 Joint Status 2 Register", 0x13A, NI_660x_READ, DATA_2B},
+ {"G2 Load B Register", 0x13C, NI_660x_WRITE, DATA_4B},
+ {"G3 Load A Register", 0x140, NI_660x_WRITE, DATA_4B},
+ {"G3 Load B Register", 0x144, NI_660x_WRITE, DATA_4B},
+ {"G2 Input Select Register", 0x148, NI_660x_WRITE, DATA_2B},
+ {"G3 Input Select Register", 0x14A, NI_660x_WRITE, DATA_2B},
+ {"G2 Autoincrement Register", 0x188, NI_660x_WRITE, DATA_2B},
+ {"G3 Autoincrement Register", 0x18A, NI_660x_WRITE, DATA_2B},
+ {"G23 Joint Reset Register", 0x190, NI_660x_WRITE, DATA_2B},
+ {"G2 Interrupt Enable", 0x192, NI_660x_WRITE, DATA_2B},
+ {"G3 Interrupt Enable", 0x196, NI_660x_WRITE, DATA_2B},
+ {"G2 Counting Mode Register", 0x1B0, NI_660x_WRITE, DATA_2B},
+ {"G3 Counting Mode Register", 0x1B2, NI_660x_WRITE, DATA_2B},
+ {"G3 Second Gate Register", 0x1B6, NI_660x_WRITE, DATA_2B},
+ {"G2 Second Gate Register", 0x1B4, NI_660x_WRITE, DATA_2B},
+ {"G2 DMA Config Register", 0x1B8, NI_660x_WRITE, DATA_2B},
+ {"G2 DMA Status Register", 0x1B8, NI_660x_READ, DATA_2B},
+ {"G3 DMA Config Register", 0x1BA, NI_660x_WRITE, DATA_2B},
+ {"G3 DMA Status Register", 0x1BA, NI_660x_READ, DATA_2B},
+ {"32 bit Digital Input", 0x414, NI_660x_READ, DATA_4B},
+ {"32 bit Digital Output", 0x510, NI_660x_WRITE, DATA_4B},
+ {"Clock Config Register", 0x73C, NI_660x_WRITE, DATA_4B},
+ {"Global Interrupt Status Register", 0x754, NI_660x_READ, DATA_4B},
+ {"DMA Configuration Register", 0x76C, NI_660x_WRITE, DATA_4B},
+ {"Global Interrupt Config Register", 0x770, NI_660x_WRITE, DATA_4B},
+ {"IO Config Register 0-1", 0x77C, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 2-3", 0x77E, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 4-5", 0x780, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 6-7", 0x782, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 8-9", 0x784, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 10-11", 0x786, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 12-13", 0x788, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 14-15", 0x78A, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 16-17", 0x78C, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 18-19", 0x78E, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 20-21", 0x790, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 22-23", 0x792, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 24-25", 0x794, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 26-27", 0x796, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 28-29", 0x798, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 30-31", 0x79A, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 32-33", 0x79C, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 34-35", 0x79E, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 36-37", 0x7A0, NI_660x_READ_WRITE, DATA_2B},
+ {"IO Config Register 38-39", 0x7A2, NI_660x_READ_WRITE, DATA_2B}
+};
+
+/* kind of ENABLE for the second counter */
+enum clock_config_register_bits {
+ CounterSwap = 0x1 << 21
+};
+
+/* ioconfigreg */
+static inline unsigned ioconfig_bitshift(unsigned pfi_channel)
+{
+ return (pfi_channel % 2) ? 0 : 8;
+}
+
+static inline unsigned pfi_output_select_mask(unsigned pfi_channel)
+{
+ return 0x3 << ioconfig_bitshift(pfi_channel);
+}
+
+static inline unsigned pfi_output_select_bits(unsigned pfi_channel,
+ unsigned output_select)
+{
+ return (output_select & 0x3) << ioconfig_bitshift(pfi_channel);
+}
+
+static inline unsigned pfi_input_select_mask(unsigned pfi_channel)
+{
+ return 0x7 << (4 + ioconfig_bitshift(pfi_channel));
+}
+
+static inline unsigned pfi_input_select_bits(unsigned pfi_channel,
+ unsigned input_select)
+{
+ return (input_select & 0x7) << (4 + ioconfig_bitshift(pfi_channel));
+}
+
+/* dma configuration register bits */
+static inline unsigned dma_select_mask(unsigned dma_channel)
+{
+ BUG_ON(dma_channel >= MAX_DMA_CHANNEL);
+ return 0x1f << (8 * dma_channel);
+}
+
+enum dma_selection {
+ dma_selection_none = 0x1f,
+};
+
+static inline unsigned dma_select_bits(unsigned dma_channel, unsigned selection)
+{
+ BUG_ON(dma_channel >= MAX_DMA_CHANNEL);
+ return (selection << (8 * dma_channel)) & dma_select_mask(dma_channel);
+}
+
+static inline unsigned dma_reset_bit(unsigned dma_channel)
+{
+ BUG_ON(dma_channel >= MAX_DMA_CHANNEL);
+ return 0x80 << (8 * dma_channel);
+}
+
+enum global_interrupt_status_register_bits {
+ Counter_0_Int_Bit = 0x100,
+ Counter_1_Int_Bit = 0x200,
+ Counter_2_Int_Bit = 0x400,
+ Counter_3_Int_Bit = 0x800,
+ Cascade_Int_Bit = 0x20000000,
+ Global_Int_Bit = 0x80000000
+};
+
+enum global_interrupt_config_register_bits {
+ Cascade_Int_Enable_Bit = 0x20000000,
+ Global_Int_Polarity_Bit = 0x40000000,
+ Global_Int_Enable_Bit = 0x80000000
+};
+
+/* Offset of the GPCT chips from the base-address of the card */
+/* First chip is at base-address + 0x00, etc. */
+static const unsigned GPCT_OFFSET[2] = { 0x0, 0x800 };
+
+enum ni_660x_boardid {
+ BOARD_PCI6601,
+ BOARD_PCI6602,
+ BOARD_PXI6602,
+ BOARD_PXI6608,
+ BOARD_PXI6624
+};
+
+struct ni_660x_board {
+ const char *name;
+ unsigned n_chips; /* total number of TIO chips */
+};
+
+static const struct ni_660x_board ni_660x_boards[] = {
+ [BOARD_PCI6601] = {
+ .name = "PCI-6601",
+ .n_chips = 1,
+ },
+ [BOARD_PCI6602] = {
+ .name = "PCI-6602",
+ .n_chips = 2,
+ },
+ [BOARD_PXI6602] = {
+ .name = "PXI-6602",
+ .n_chips = 2,
+ },
+ [BOARD_PXI6608] = {
+ .name = "PXI-6608",
+ .n_chips = 2,
+ },
+ [BOARD_PXI6624] = {
+ .name = "PXI-6624",
+ .n_chips = 2,
+ },
+};
+
+#define NI_660X_MAX_NUM_CHIPS 2
+#define NI_660X_MAX_NUM_COUNTERS (NI_660X_MAX_NUM_CHIPS * counters_per_chip)
+
+struct ni_660x_private {
+ struct mite_struct *mite;
+ struct ni_gpct_device *counter_dev;
+ uint64_t pfi_direction_bits;
+ struct mite_dma_descriptor_ring
+ *mite_rings[NI_660X_MAX_NUM_CHIPS][counters_per_chip];
+ spinlock_t mite_channel_lock;
+ /* interrupt_lock prevents races between interrupt and comedi_poll */
+ spinlock_t interrupt_lock;
+ unsigned dma_configuration_soft_copies[NI_660X_MAX_NUM_CHIPS];
+ spinlock_t soft_reg_copy_lock;
+ unsigned short pfi_output_selects[NUM_PFI_CHANNELS];
+};
+
+static inline unsigned ni_660x_num_counters(struct comedi_device *dev)
+{
+ const struct ni_660x_board *board = dev->board_ptr;
+
+ return board->n_chips * counters_per_chip;
+}
+
+static enum ni_660x_register ni_gpct_to_660x_register(enum ni_gpct_register reg)
+{
+ switch (reg) {
+ case NITIO_G0_AUTO_INC:
+ return NI660X_G0_AUTO_INC;
+ case NITIO_G1_AUTO_INC:
+ return NI660X_G1_AUTO_INC;
+ case NITIO_G2_AUTO_INC:
+ return NI660X_G2_AUTO_INC;
+ case NITIO_G3_AUTO_INC:
+ return NI660X_G3_AUTO_INC;
+ case NITIO_G0_CMD:
+ return NI660X_G0_CMD;
+ case NITIO_G1_CMD:
+ return NI660X_G1_CMD;
+ case NITIO_G2_CMD:
+ return NI660X_G2_CMD;
+ case NITIO_G3_CMD:
+ return NI660X_G3_CMD;
+ case NITIO_G0_HW_SAVE:
+ return NI660X_G0_HW_SAVE;
+ case NITIO_G1_HW_SAVE:
+ return NI660X_G1_HW_SAVE;
+ case NITIO_G2_HW_SAVE:
+ return NI660X_G2_HW_SAVE;
+ case NITIO_G3_HW_SAVE:
+ return NI660X_G3_HW_SAVE;
+ case NITIO_G0_SW_SAVE:
+ return NI660X_G0_SW_SAVE;
+ case NITIO_G1_SW_SAVE:
+ return NI660X_G1_SW_SAVE;
+ case NITIO_G2_SW_SAVE:
+ return NI660X_G2_SW_SAVE;
+ case NITIO_G3_SW_SAVE:
+ return NI660X_G3_SW_SAVE;
+ case NITIO_G0_MODE:
+ return NI660X_G0_MODE;
+ case NITIO_G1_MODE:
+ return NI660X_G1_MODE;
+ case NITIO_G2_MODE:
+ return NI660X_G2_MODE;
+ case NITIO_G3_MODE:
+ return NI660X_G3_MODE;
+ case NITIO_G0_LOADA:
+ return NI660X_G0_LOADA;
+ case NITIO_G1_LOADA:
+ return NI660X_G1_LOADA;
+ case NITIO_G2_LOADA:
+ return NI660X_G2_LOADA;
+ case NITIO_G3_LOADA:
+ return NI660X_G3_LOADA;
+ case NITIO_G0_LOADB:
+ return NI660X_G0_LOADB;
+ case NITIO_G1_LOADB:
+ return NI660X_G1_LOADB;
+ case NITIO_G2_LOADB:
+ return NI660X_G2_LOADB;
+ case NITIO_G3_LOADB:
+ return NI660X_G3_LOADB;
+ case NITIO_G0_INPUT_SEL:
+ return NI660X_G0_INPUT_SEL;
+ case NITIO_G1_INPUT_SEL:
+ return NI660X_G1_INPUT_SEL;
+ case NITIO_G2_INPUT_SEL:
+ return NI660X_G2_INPUT_SEL;
+ case NITIO_G3_INPUT_SEL:
+ return NI660X_G3_INPUT_SEL;
+ case NITIO_G01_STATUS:
+ return NI660X_G01_STATUS;
+ case NITIO_G23_STATUS:
+ return NI660X_G23_STATUS;
+ case NITIO_G01_RESET:
+ return NI660X_G01_RESET;
+ case NITIO_G23_RESET:
+ return NI660X_G23_RESET;
+ case NITIO_G01_STATUS1:
+ return NI660X_G01_STATUS1;
+ case NITIO_G23_STATUS1:
+ return NI660X_G23_STATUS1;
+ case NITIO_G01_STATUS2:
+ return NI660X_G01_STATUS2;
+ case NITIO_G23_STATUS2:
+ return NI660X_G23_STATUS2;
+ case NITIO_G0_CNT_MODE:
+ return NI660X_G0_CNT_MODE;
+ case NITIO_G1_CNT_MODE:
+ return NI660X_G1_CNT_MODE;
+ case NITIO_G2_CNT_MODE:
+ return NI660X_G2_CNT_MODE;
+ case NITIO_G3_CNT_MODE:
+ return NI660X_G3_CNT_MODE;
+ case NITIO_G0_GATE2:
+ return NI660X_G0_GATE2;
+ case NITIO_G1_GATE2:
+ return NI660X_G1_GATE2;
+ case NITIO_G2_GATE2:
+ return NI660X_G2_GATE2;
+ case NITIO_G3_GATE2:
+ return NI660X_G3_GATE2;
+ case NITIO_G0_DMA_CFG:
+ return NI660X_G0_DMA_CFG;
+ case NITIO_G0_DMA_STATUS:
+ return NI660X_G0_DMA_STATUS;
+ case NITIO_G1_DMA_CFG:
+ return NI660X_G1_DMA_CFG;
+ case NITIO_G1_DMA_STATUS:
+ return NI660X_G1_DMA_STATUS;
+ case NITIO_G2_DMA_CFG:
+ return NI660X_G2_DMA_CFG;
+ case NITIO_G2_DMA_STATUS:
+ return NI660X_G2_DMA_STATUS;
+ case NITIO_G3_DMA_CFG:
+ return NI660X_G3_DMA_CFG;
+ case NITIO_G3_DMA_STATUS:
+ return NI660X_G3_DMA_STATUS;
+ case NITIO_G0_INT_ACK:
+ return NI660X_G0_INT_ACK;
+ case NITIO_G1_INT_ACK:
+ return NI660X_G1_INT_ACK;
+ case NITIO_G2_INT_ACK:
+ return NI660X_G2_INT_ACK;
+ case NITIO_G3_INT_ACK:
+ return NI660X_G3_INT_ACK;
+ case NITIO_G0_STATUS:
+ return NI660X_G0_STATUS;
+ case NITIO_G1_STATUS:
+ return NI660X_G1_STATUS;
+ case NITIO_G2_STATUS:
+ return NI660X_G2_STATUS;
+ case NITIO_G3_STATUS:
+ return NI660X_G3_STATUS;
+ case NITIO_G0_INT_ENA:
+ return NI660X_G0_INT_ENA;
+ case NITIO_G1_INT_ENA:
+ return NI660X_G1_INT_ENA;
+ case NITIO_G2_INT_ENA:
+ return NI660X_G2_INT_ENA;
+ case NITIO_G3_INT_ENA:
+ return NI660X_G3_INT_ENA;
+ default:
+ BUG();
+ return 0;
+ }
+}
+
+static inline void ni_660x_write_register(struct comedi_device *dev,
+ unsigned chip, unsigned bits,
+ enum ni_660x_register reg)
+{
+ unsigned int addr = GPCT_OFFSET[chip] + registerData[reg].offset;
+
+ switch (registerData[reg].size) {
+ case DATA_2B:
+ writew(bits, dev->mmio + addr);
+ break;
+ case DATA_4B:
+ writel(bits, dev->mmio + addr);
+ break;
+ default:
+ BUG();
+ break;
+ }
+}
+
+static inline unsigned ni_660x_read_register(struct comedi_device *dev,
+ unsigned chip,
+ enum ni_660x_register reg)
+{
+ unsigned int addr = GPCT_OFFSET[chip] + registerData[reg].offset;
+
+ switch (registerData[reg].size) {
+ case DATA_2B:
+ return readw(dev->mmio + addr);
+ case DATA_4B:
+ return readl(dev->mmio + addr);
+ default:
+ BUG();
+ break;
+ }
+ return 0;
+}
+
+static void ni_gpct_write_register(struct ni_gpct *counter, unsigned bits,
+ enum ni_gpct_register reg)
+{
+ struct comedi_device *dev = counter->counter_dev->dev;
+ enum ni_660x_register ni_660x_register = ni_gpct_to_660x_register(reg);
+ unsigned chip = counter->chip_index;
+
+ ni_660x_write_register(dev, chip, bits, ni_660x_register);
+}
+
+static unsigned ni_gpct_read_register(struct ni_gpct *counter,
+ enum ni_gpct_register reg)
+{
+ struct comedi_device *dev = counter->counter_dev->dev;
+ enum ni_660x_register ni_660x_register = ni_gpct_to_660x_register(reg);
+ unsigned chip = counter->chip_index;
+
+ return ni_660x_read_register(dev, chip, ni_660x_register);
+}
+
+static inline struct mite_dma_descriptor_ring *mite_ring(struct ni_660x_private
+ *priv,
+ struct ni_gpct
+ *counter)
+{
+ unsigned chip = counter->chip_index;
+
+ return priv->mite_rings[chip][counter->counter_index];
+}
+
+static inline void ni_660x_set_dma_channel(struct comedi_device *dev,
+ unsigned mite_channel,
+ struct ni_gpct *counter)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned chip = counter->chip_index;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags);
+ devpriv->dma_configuration_soft_copies[chip] &=
+ ~dma_select_mask(mite_channel);
+ devpriv->dma_configuration_soft_copies[chip] |=
+ dma_select_bits(mite_channel, counter->counter_index);
+ ni_660x_write_register(dev, chip,
+ devpriv->dma_configuration_soft_copies[chip] |
+ dma_reset_bit(mite_channel), NI660X_DMA_CFG);
+ mmiowb();
+ spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags);
+}
+
+static inline void ni_660x_unset_dma_channel(struct comedi_device *dev,
+ unsigned mite_channel,
+ struct ni_gpct *counter)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned chip = counter->chip_index;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags);
+ devpriv->dma_configuration_soft_copies[chip] &=
+ ~dma_select_mask(mite_channel);
+ devpriv->dma_configuration_soft_copies[chip] |=
+ dma_select_bits(mite_channel, dma_selection_none);
+ ni_660x_write_register(dev, chip,
+ devpriv->dma_configuration_soft_copies[chip],
+ NI660X_DMA_CFG);
+ mmiowb();
+ spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags);
+}
+
+static int ni_660x_request_mite_channel(struct comedi_device *dev,
+ struct ni_gpct *counter,
+ enum comedi_io_direction direction)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned long flags;
+ struct mite_channel *mite_chan;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ BUG_ON(counter->mite_chan);
+ mite_chan = mite_request_channel(devpriv->mite,
+ mite_ring(devpriv, counter));
+ if (!mite_chan) {
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ dev_err(dev->class_dev,
+ "failed to reserve mite dma channel for counter\n");
+ return -EBUSY;
+ }
+ mite_chan->dir = direction;
+ ni_tio_set_mite_channel(counter, mite_chan);
+ ni_660x_set_dma_channel(dev, mite_chan->channel, counter);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ return 0;
+}
+
+static void ni_660x_release_mite_channel(struct comedi_device *dev,
+ struct ni_gpct *counter)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (counter->mite_chan) {
+ struct mite_channel *mite_chan = counter->mite_chan;
+
+ ni_660x_unset_dma_channel(dev, mite_chan->channel, counter);
+ ni_tio_set_mite_channel(counter, NULL);
+ mite_release_channel(mite_chan);
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+}
+
+static int ni_660x_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ int retval;
+
+ retval = ni_660x_request_mite_channel(dev, counter, COMEDI_INPUT);
+ if (retval) {
+ dev_err(dev->class_dev,
+ "no dma channel available for use by counter\n");
+ return retval;
+ }
+ ni_tio_acknowledge(counter);
+
+ return ni_tio_cmd(dev, s);
+}
+
+static int ni_660x_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ int retval;
+
+ retval = ni_tio_cancel(counter);
+ ni_660x_release_mite_channel(dev, counter);
+ return retval;
+}
+
+static void set_tio_counterswap(struct comedi_device *dev, int chip)
+{
+ unsigned bits = 0;
+
+ /*
+ * See P. 3.5 of the Register-Level Programming manual.
+ * The CounterSwap bit has to be set on the second chip,
+ * otherwise it will try to use the same pins as the
+ * first chip.
+ */
+ if (chip)
+ bits = CounterSwap;
+
+ ni_660x_write_register(dev, chip, bits, NI660X_CLK_CFG);
+}
+
+static void ni_660x_handle_gpct_interrupt(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+
+ ni_tio_handle_interrupt(counter, s);
+ comedi_handle_events(dev, s);
+}
+
+static irqreturn_t ni_660x_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct ni_660x_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ unsigned i;
+ unsigned long flags;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+ /* lock to avoid race with comedi_poll */
+ spin_lock_irqsave(&devpriv->interrupt_lock, flags);
+ smp_mb();
+ for (i = 0; i < ni_660x_num_counters(dev); ++i) {
+ s = &dev->subdevices[NI_660X_GPCT_SUBDEV(i)];
+ ni_660x_handle_gpct_interrupt(dev, s);
+ }
+ spin_unlock_irqrestore(&devpriv->interrupt_lock, flags);
+ return IRQ_HANDLED;
+}
+
+static int ni_660x_input_poll(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ struct ni_gpct *counter = s->private;
+ unsigned long flags;
+
+ /* lock to avoid race with comedi_poll */
+ spin_lock_irqsave(&devpriv->interrupt_lock, flags);
+ mite_sync_input_dma(counter->mite_chan, s);
+ spin_unlock_irqrestore(&devpriv->interrupt_lock, flags);
+ return comedi_buf_read_n_available(s);
+}
+
+static int ni_660x_buf_change(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ struct ni_gpct *counter = s->private;
+ int ret;
+
+ ret = mite_buf_change(mite_ring(devpriv, counter), s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ni_660x_allocate_private(struct comedi_device *dev)
+{
+ struct ni_660x_private *devpriv;
+ unsigned i;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->mite_channel_lock);
+ spin_lock_init(&devpriv->interrupt_lock);
+ spin_lock_init(&devpriv->soft_reg_copy_lock);
+ for (i = 0; i < NUM_PFI_CHANNELS; ++i)
+ devpriv->pfi_output_selects[i] = pfi_output_select_counter;
+
+ return 0;
+}
+
+static int ni_660x_alloc_mite_rings(struct comedi_device *dev)
+{
+ const struct ni_660x_board *board = dev->board_ptr;
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned i;
+ unsigned j;
+
+ for (i = 0; i < board->n_chips; ++i) {
+ for (j = 0; j < counters_per_chip; ++j) {
+ devpriv->mite_rings[i][j] =
+ mite_alloc_ring(devpriv->mite);
+ if (!devpriv->mite_rings[i][j])
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static void ni_660x_free_mite_rings(struct comedi_device *dev)
+{
+ const struct ni_660x_board *board = dev->board_ptr;
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned i;
+ unsigned j;
+
+ for (i = 0; i < board->n_chips; ++i) {
+ for (j = 0; j < counters_per_chip; ++j)
+ mite_free_ring(devpriv->mite_rings[i][j]);
+ }
+}
+
+static void init_tio_chip(struct comedi_device *dev, int chipset)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned i;
+
+ /* init dma configuration register */
+ devpriv->dma_configuration_soft_copies[chipset] = 0;
+ for (i = 0; i < MAX_DMA_CHANNEL; ++i) {
+ devpriv->dma_configuration_soft_copies[chipset] |=
+ dma_select_bits(i, dma_selection_none) & dma_select_mask(i);
+ }
+ ni_660x_write_register(dev, chipset,
+ devpriv->dma_configuration_soft_copies[chipset],
+ NI660X_DMA_CFG);
+ for (i = 0; i < NUM_PFI_CHANNELS; ++i)
+ ni_660x_write_register(dev, chipset, 0, IOConfigReg(i));
+}
+
+static int ni_660x_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned base_bitfield_channel = CR_CHAN(insn->chanspec);
+
+ /* Check if we have to write some bits */
+ if (data[0]) {
+ s->state &= ~(data[0] << base_bitfield_channel);
+ s->state |= (data[0] & data[1]) << base_bitfield_channel;
+ /* Write out the new digital output lines */
+ ni_660x_write_register(dev, 0, s->state, NI660X_DIO32_OUTPUT);
+ }
+ /* on return, data[1] contains the value of the digital
+ * input and output lines. */
+ data[1] = (ni_660x_read_register(dev, 0, NI660X_DIO32_INPUT) >>
+ base_bitfield_channel);
+
+ return insn->n;
+}
+
+static void ni_660x_select_pfi_output(struct comedi_device *dev,
+ unsigned pfi_channel,
+ unsigned output_select)
+{
+ const struct ni_660x_board *board = dev->board_ptr;
+ static const unsigned counter_4_7_first_pfi = 8;
+ static const unsigned counter_4_7_last_pfi = 23;
+ unsigned active_chipset = 0;
+ unsigned idle_chipset = 0;
+ unsigned active_bits;
+ unsigned idle_bits;
+
+ if (board->n_chips > 1) {
+ if (output_select == pfi_output_select_counter &&
+ pfi_channel >= counter_4_7_first_pfi &&
+ pfi_channel <= counter_4_7_last_pfi) {
+ active_chipset = 1;
+ idle_chipset = 0;
+ } else {
+ active_chipset = 0;
+ idle_chipset = 1;
+ }
+ }
+
+ if (idle_chipset != active_chipset) {
+ idle_bits =
+ ni_660x_read_register(dev, idle_chipset,
+ IOConfigReg(pfi_channel));
+ idle_bits &= ~pfi_output_select_mask(pfi_channel);
+ idle_bits |=
+ pfi_output_select_bits(pfi_channel,
+ pfi_output_select_high_Z);
+ ni_660x_write_register(dev, idle_chipset, idle_bits,
+ IOConfigReg(pfi_channel));
+ }
+
+ active_bits =
+ ni_660x_read_register(dev, active_chipset,
+ IOConfigReg(pfi_channel));
+ active_bits &= ~pfi_output_select_mask(pfi_channel);
+ active_bits |= pfi_output_select_bits(pfi_channel, output_select);
+ ni_660x_write_register(dev, active_chipset, active_bits,
+ IOConfigReg(pfi_channel));
+}
+
+static int ni_660x_set_pfi_routing(struct comedi_device *dev, unsigned chan,
+ unsigned source)
+{
+ struct ni_660x_private *devpriv = dev->private;
+
+ if (source > num_pfi_output_selects)
+ return -EINVAL;
+ if (source == pfi_output_select_high_Z)
+ return -EINVAL;
+ if (chan < min_counter_pfi_chan) {
+ if (source == pfi_output_select_counter)
+ return -EINVAL;
+ } else if (chan > max_dio_pfi_chan) {
+ if (source == pfi_output_select_do)
+ return -EINVAL;
+ }
+
+ devpriv->pfi_output_selects[chan] = source;
+ if (devpriv->pfi_direction_bits & (((uint64_t) 1) << chan))
+ ni_660x_select_pfi_output(dev, chan,
+ devpriv->pfi_output_selects[chan]);
+ return 0;
+}
+
+static int ni_660x_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint64_t bit = 1ULL << chan;
+ unsigned int val;
+ int ret;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIO_OUTPUT:
+ devpriv->pfi_direction_bits |= bit;
+ ni_660x_select_pfi_output(dev, chan,
+ devpriv->pfi_output_selects[chan]);
+ break;
+
+ case INSN_CONFIG_DIO_INPUT:
+ devpriv->pfi_direction_bits &= ~bit;
+ ni_660x_select_pfi_output(dev, chan, pfi_output_select_high_Z);
+ break;
+
+ case INSN_CONFIG_DIO_QUERY:
+ data[1] = (devpriv->pfi_direction_bits & bit) ? COMEDI_OUTPUT
+ : COMEDI_INPUT;
+ break;
+
+ case INSN_CONFIG_SET_ROUTING:
+ ret = ni_660x_set_pfi_routing(dev, chan, data[1]);
+ if (ret)
+ return ret;
+ break;
+
+ case INSN_CONFIG_GET_ROUTING:
+ data[1] = devpriv->pfi_output_selects[chan];
+ break;
+
+ case INSN_CONFIG_FILTER:
+ val = ni_660x_read_register(dev, 0, IOConfigReg(chan));
+ val &= ~pfi_input_select_mask(chan);
+ val |= pfi_input_select_bits(chan, data[1]);
+ ni_660x_write_register(dev, 0, val, IOConfigReg(chan));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int ni_660x_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_660x_board *board = NULL;
+ struct ni_660x_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ unsigned i;
+ unsigned global_interrupt_config_bits;
+
+ if (context < ARRAY_SIZE(ni_660x_boards))
+ board = &ni_660x_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = ni_660x_allocate_private(dev);
+ if (ret < 0)
+ return ret;
+ devpriv = dev->private;
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ ret = mite_setup2(dev, devpriv->mite, true);
+ if (ret < 0)
+ return ret;
+
+ ret = ni_660x_alloc_mite_rings(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2 + NI_660X_MAX_NUM_COUNTERS);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* Old GENERAL-PURPOSE COUNTER/TIME (GPCT) subdevice, no longer used */
+ s->type = COMEDI_SUBD_UNUSED;
+
+ s = &dev->subdevices[NI_660X_DIO_SUBDEV];
+ /* DIGITAL I/O SUBDEVICE */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = NUM_PFI_CHANNELS;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni_660x_dio_insn_bits;
+ s->insn_config = ni_660x_dio_insn_config;
+ /* we use the ioconfig registers to control dio direction, so zero
+ output enables in stc dio control reg */
+ ni_660x_write_register(dev, 0, 0, NI660X_STC_DIO_CONTROL);
+
+ devpriv->counter_dev = ni_gpct_device_construct(dev,
+ &ni_gpct_write_register,
+ &ni_gpct_read_register,
+ ni_gpct_variant_660x,
+ ni_660x_num_counters
+ (dev));
+ if (!devpriv->counter_dev)
+ return -ENOMEM;
+ for (i = 0; i < NI_660X_MAX_NUM_COUNTERS; ++i) {
+ s = &dev->subdevices[NI_660X_GPCT_SUBDEV(i)];
+ if (i < ni_660x_num_counters(dev)) {
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE |
+ SDF_LSAMPL | SDF_CMD_READ;
+ s->n_chan = 3;
+ s->maxdata = 0xffffffff;
+ s->insn_read = ni_tio_insn_read;
+ s->insn_write = ni_tio_insn_write;
+ s->insn_config = ni_tio_insn_config;
+ s->do_cmd = &ni_660x_cmd;
+ s->len_chanlist = 1;
+ s->do_cmdtest = ni_tio_cmdtest;
+ s->cancel = &ni_660x_cancel;
+ s->poll = &ni_660x_input_poll;
+ s->async_dma_dir = DMA_BIDIRECTIONAL;
+ s->buf_change = &ni_660x_buf_change;
+ s->private = &devpriv->counter_dev->counters[i];
+
+ devpriv->counter_dev->counters[i].chip_index =
+ i / counters_per_chip;
+ devpriv->counter_dev->counters[i].counter_index =
+ i % counters_per_chip;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+ }
+ for (i = 0; i < board->n_chips; ++i)
+ init_tio_chip(dev, i);
+
+ for (i = 0; i < ni_660x_num_counters(dev); ++i)
+ ni_tio_init_counter(&devpriv->counter_dev->counters[i]);
+
+ for (i = 0; i < NUM_PFI_CHANNELS; ++i) {
+ if (i < min_counter_pfi_chan)
+ ni_660x_set_pfi_routing(dev, i, pfi_output_select_do);
+ else
+ ni_660x_set_pfi_routing(dev, i,
+ pfi_output_select_counter);
+ ni_660x_select_pfi_output(dev, i, pfi_output_select_high_Z);
+ }
+ /* to be safe, set counterswap bits on tio chips after all the counter
+ outputs have been set to high impedance mode */
+ for (i = 0; i < board->n_chips; ++i)
+ set_tio_counterswap(dev, i);
+
+ ret = request_irq(pcidev->irq, ni_660x_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret < 0) {
+ dev_warn(dev->class_dev, " irq not available\n");
+ return ret;
+ }
+ dev->irq = pcidev->irq;
+ global_interrupt_config_bits = Global_Int_Enable_Bit;
+ if (board->n_chips > 1)
+ global_interrupt_config_bits |= Cascade_Int_Enable_Bit;
+ ni_660x_write_register(dev, 0, global_interrupt_config_bits,
+ NI660X_GLOBAL_INT_CFG);
+
+ return 0;
+}
+
+static void ni_660x_detach(struct comedi_device *dev)
+{
+ struct ni_660x_private *devpriv = dev->private;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->counter_dev)
+ ni_gpct_device_destroy(devpriv->counter_dev);
+ ni_660x_free_mite_rings(dev);
+ mite_detach(devpriv->mite);
+ }
+ if (dev->mmio)
+ iounmap(dev->mmio);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver ni_660x_driver = {
+ .driver_name = "ni_660x",
+ .module = THIS_MODULE,
+ .auto_attach = ni_660x_auto_attach,
+ .detach = ni_660x_detach,
+};
+
+static int ni_660x_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_660x_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_660x_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1310), BOARD_PCI6602 },
+ { PCI_VDEVICE(NI, 0x1360), BOARD_PXI6602 },
+ { PCI_VDEVICE(NI, 0x2c60), BOARD_PCI6601 },
+ { PCI_VDEVICE(NI, 0x2cc0), BOARD_PXI6608 },
+ { PCI_VDEVICE(NI, 0x1e40), BOARD_PXI6624 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_660x_pci_table);
+
+static struct pci_driver ni_660x_pci_driver = {
+ .name = "ni_660x",
+ .id_table = ni_660x_pci_table,
+ .probe = ni_660x_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_660x_driver, ni_660x_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_670x.c b/drivers/staging/comedi/drivers/ni_670x.c
new file mode 100644
index 000000000..13c6ccb1f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_670x.c
@@ -0,0 +1,296 @@
+/*
+ comedi/drivers/ni_670x.c
+ Hardware driver for NI 670x devices
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2001 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_670x
+Description: National Instruments 670x
+Author: Bart Joris <bjoris@advalvas.be>
+Updated: Wed, 11 Dec 2002 18:25:35 -0800
+Devices: [National Instruments] PCI-6703 (ni_670x), PCI-6704
+Status: unknown
+
+Commands are not supported.
+*/
+
+/*
+ Bart Joris <bjoris@advalvas.be> Last updated on 20/08/2001
+
+ Manuals:
+
+ 322110a.pdf PCI/PXI-6704 User Manual
+ 322110b.pdf PCI/PXI-6703/6704 User Manual
+
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include "../comedi_pci.h"
+
+#define AO_VALUE_OFFSET 0x00
+#define AO_CHAN_OFFSET 0x0c
+#define AO_STATUS_OFFSET 0x10
+#define AO_CONTROL_OFFSET 0x10
+#define DIO_PORT0_DIR_OFFSET 0x20
+#define DIO_PORT0_DATA_OFFSET 0x24
+#define DIO_PORT1_DIR_OFFSET 0x28
+#define DIO_PORT1_DATA_OFFSET 0x2c
+#define MISC_STATUS_OFFSET 0x14
+#define MISC_CONTROL_OFFSET 0x14
+
+enum ni_670x_boardid {
+ BOARD_PCI6703,
+ BOARD_PXI6704,
+ BOARD_PCI6704,
+};
+
+struct ni_670x_board {
+ const char *name;
+ unsigned short ao_chans;
+};
+
+static const struct ni_670x_board ni_670x_boards[] = {
+ [BOARD_PCI6703] = {
+ .name = "PCI-6703",
+ .ao_chans = 16,
+ },
+ [BOARD_PXI6704] = {
+ .name = "PXI-6704",
+ .ao_chans = 32,
+ },
+ [BOARD_PCI6704] = {
+ .name = "PCI-6704",
+ .ao_chans = 32,
+ },
+};
+
+struct ni_670x_private {
+ int boardtype;
+ int dio;
+};
+
+static int ni_670x_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ /*
+ * Channel number mapping:
+ *
+ * NI 6703/ NI 6704 | NI 6704 Only
+ * -------------------------------
+ * vch(0) : 0 | ich(16) : 1
+ * vch(1) : 2 | ich(17) : 3
+ * ... | ...
+ * vch(15) : 30 | ich(31) : 31
+ */
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ /* First write in channel register which channel to use */
+ writel(((chan & 15) << 1) | ((chan & 16) >> 4),
+ dev->mmio + AO_CHAN_OFFSET);
+ /* write channel value */
+ writel(val, dev->mmio + AO_VALUE_OFFSET);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int ni_670x_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ writel(s->state, dev->mmio + DIO_PORT0_DATA_OFFSET);
+
+ data[1] = readl(dev->mmio + DIO_PORT0_DATA_OFFSET);
+
+ return insn->n;
+}
+
+static int ni_670x_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ writel(s->io_bits, dev->mmio + DIO_PORT0_DIR_OFFSET);
+
+ return insn->n;
+}
+
+/* ripped from mite.h and mite_setup2() to avoid mite dependency */
+#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */
+#define WENAB (1 << 7) /* window enable */
+
+static int ni_670x_mite_init(struct pci_dev *pcidev)
+{
+ void __iomem *mite_base;
+ u32 main_phys_addr;
+
+ /* ioremap the MITE registers (BAR 0) temporarily */
+ mite_base = pci_ioremap_bar(pcidev, 0);
+ if (!mite_base)
+ return -ENOMEM;
+
+ /* set data window to main registers (BAR 1) */
+ main_phys_addr = pci_resource_start(pcidev, 1);
+ writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR);
+
+ /* finished with MITE registers */
+ iounmap(mite_base);
+ return 0;
+}
+
+static int ni_670x_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_670x_board *thisboard = NULL;
+ struct ni_670x_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ if (context < ARRAY_SIZE(ni_670x_boards))
+ thisboard = &ni_670x_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = ni_670x_mite_init(pcidev);
+ if (ret)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 1);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = thisboard->ao_chans;
+ s->maxdata = 0xffff;
+ if (s->n_chan == 32) {
+ const struct comedi_lrange **range_table_list;
+
+ range_table_list = kmalloc(sizeof(struct comedi_lrange *) * 32,
+ GFP_KERNEL);
+ if (!range_table_list)
+ return -ENOMEM;
+ s->range_table_list = range_table_list;
+ for (i = 0; i < 16; i++) {
+ range_table_list[i] = &range_bipolar10;
+ range_table_list[16 + i] = &range_0_20mA;
+ }
+ } else {
+ s->range_table = &range_bipolar10;
+ }
+ s->insn_write = ni_670x_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[1];
+ /* digital i/o subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni_670x_dio_insn_bits;
+ s->insn_config = ni_670x_dio_insn_config;
+
+ /* Config of misc registers */
+ writel(0x10, dev->mmio + MISC_CONTROL_OFFSET);
+ /* Config of ao registers */
+ writel(0x00, dev->mmio + AO_CONTROL_OFFSET);
+
+ return 0;
+}
+
+static void ni_670x_detach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+
+ comedi_pci_detach(dev);
+ if (dev->n_subdevices) {
+ s = &dev->subdevices[0];
+ if (s)
+ kfree(s->range_table_list);
+ }
+}
+
+static struct comedi_driver ni_670x_driver = {
+ .driver_name = "ni_670x",
+ .module = THIS_MODULE,
+ .auto_attach = ni_670x_auto_attach,
+ .detach = ni_670x_detach,
+};
+
+static int ni_670x_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_670x_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_670x_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1290), BOARD_PCI6704 },
+ { PCI_VDEVICE(NI, 0x1920), BOARD_PXI6704 },
+ { PCI_VDEVICE(NI, 0x2c90), BOARD_PCI6703 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_670x_pci_table);
+
+static struct pci_driver ni_670x_pci_driver = {
+ .name = "ni_670x",
+ .id_table = ni_670x_pci_table,
+ .probe = ni_670x_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_670x_driver, ni_670x_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_at_a2150.c b/drivers/staging/comedi/drivers/ni_at_a2150.c
new file mode 100644
index 000000000..3a972d153
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_at_a2150.c
@@ -0,0 +1,796 @@
+/*
+ comedi/drivers/ni_at_a2150.c
+ Driver for National Instruments AT-A2150 boards
+ Copyright (C) 2001, 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_at_a2150
+Description: National Instruments AT-A2150
+Author: Frank Mori Hess
+Status: works
+Devices: [National Instruments] AT-A2150C (at_a2150c), AT-2150S (at_a2150s)
+
+If you want to ac couple the board's inputs, use AREF_OTHER.
+
+Configuration options:
+ [0] - I/O port base address
+ [1] - IRQ (optional, required for timed conversions)
+ [2] - DMA (optional, required for timed conversions)
+
+*/
+/*
+Yet another driver for obsolete hardware brought to you by Frank Hess.
+Testing and debugging help provided by Dave Andruczyk.
+
+This driver supports the boards:
+
+AT-A2150C
+AT-A2150S
+
+The only difference is their master clock frequencies.
+
+Options:
+ [0] - base io address
+ [1] - irq
+ [2] - dma channel
+
+References (from ftp://ftp.natinst.com/support/manuals):
+
+ 320360.pdf AT-A2150 User Manual
+
+TODO:
+
+analog level triggering
+TRIG_WAKE_EOS
+
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+#define A2150_DMA_BUFFER_SIZE 0xff00 /* size in bytes of dma buffer */
+
+/* Registers and bits */
+#define CONFIG_REG 0x0
+#define CHANNEL_BITS(x) ((x) & 0x7)
+#define CHANNEL_MASK 0x7
+#define CLOCK_SELECT_BITS(x) (((x) & 0x3) << 3)
+#define CLOCK_DIVISOR_BITS(x) (((x) & 0x3) << 5)
+#define CLOCK_MASK (0xf << 3)
+#define ENABLE0_BIT 0x80 /* enable (don't internally ground) channels 0 and 1 */
+#define ENABLE1_BIT 0x100 /* enable (don't internally ground) channels 2 and 3 */
+#define AC0_BIT 0x200 /* ac couple channels 0,1 */
+#define AC1_BIT 0x400 /* ac couple channels 2,3 */
+#define APD_BIT 0x800 /* analog power down */
+#define DPD_BIT 0x1000 /* digital power down */
+#define TRIGGER_REG 0x2 /* trigger config register */
+#define POST_TRIGGER_BITS 0x2
+#define DELAY_TRIGGER_BITS 0x3
+#define HW_TRIG_EN 0x10 /* enable hardware trigger */
+#define FIFO_START_REG 0x6 /* software start aquistion trigger */
+#define FIFO_RESET_REG 0x8 /* clears fifo + fifo flags */
+#define FIFO_DATA_REG 0xa /* read data */
+#define DMA_TC_CLEAR_REG 0xe /* clear dma terminal count interrupt */
+#define STATUS_REG 0x12 /* read only */
+#define FNE_BIT 0x1 /* fifo not empty */
+#define OVFL_BIT 0x8 /* fifo overflow */
+#define EDAQ_BIT 0x10 /* end of acquisition interrupt */
+#define DCAL_BIT 0x20 /* offset calibration in progress */
+#define INTR_BIT 0x40 /* interrupt has occurred */
+#define DMA_TC_BIT 0x80 /* dma terminal count interrupt has occurred */
+#define ID_BITS(x) (((x) >> 8) & 0x3)
+#define IRQ_DMA_CNTRL_REG 0x12 /* write only */
+#define DMA_CHAN_BITS(x) ((x) & 0x7) /* sets dma channel */
+#define DMA_EN_BIT 0x8 /* enables dma */
+#define IRQ_LVL_BITS(x) (((x) & 0xf) << 4) /* sets irq level */
+#define FIFO_INTR_EN_BIT 0x100 /* enable fifo interrupts */
+#define FIFO_INTR_FHF_BIT 0x200 /* interrupt fifo half full */
+#define DMA_INTR_EN_BIT 0x800 /* enable interrupt on dma terminal count */
+#define DMA_DEM_EN_BIT 0x1000 /* enables demand mode dma */
+#define I8253_BASE_REG 0x14
+
+struct a2150_board {
+ const char *name;
+ int clock[4]; /* master clock periods, in nanoseconds */
+ int num_clocks; /* number of available master clock speeds */
+ int ai_speed; /* maximum conversion rate in nanoseconds */
+};
+
+/* analog input range */
+static const struct comedi_lrange range_a2150 = {
+ 1, {
+ BIP_RANGE(2.828)
+ }
+};
+
+/* enum must match board indices */
+enum { a2150_c, a2150_s };
+static const struct a2150_board a2150_boards[] = {
+ {
+ .name = "at-a2150c",
+ .clock = {31250, 22676, 20833, 19531},
+ .num_clocks = 4,
+ .ai_speed = 19531,
+ },
+ {
+ .name = "at-a2150s",
+ .clock = {62500, 50000, 41667, 0},
+ .num_clocks = 3,
+ .ai_speed = 41667,
+ },
+};
+
+struct a2150_private {
+ struct comedi_isadma *dma;
+ unsigned int count; /* number of data points left to be taken */
+ int irq_dma_bits; /* irq/dma register bits */
+ int config_bits; /* config register bits */
+};
+
+/* interrupt service routine */
+static irqreturn_t a2150_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct a2150_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[0];
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned short *buf = desc->virt_addr;
+ unsigned int max_points, num_points, residue, leftover;
+ unsigned short dpnt;
+ int status;
+ int i;
+
+ if (!dev->attached)
+ return IRQ_HANDLED;
+
+ status = inw(dev->iobase + STATUS_REG);
+ if ((status & INTR_BIT) == 0)
+ return IRQ_NONE;
+
+ if (status & OVFL_BIT) {
+ async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+ }
+
+ if ((status & DMA_TC_BIT) == 0) {
+ async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+ }
+
+ /*
+ * residue is the number of bytes left to be done on the dma
+ * transfer. It should always be zero at this point unless
+ * the stop_src is set to external triggering.
+ */
+ residue = comedi_isadma_disable(desc->chan);
+
+ /* figure out how many points to read */
+ max_points = comedi_bytes_to_samples(s, desc->size);
+ num_points = max_points - comedi_bytes_to_samples(s, residue);
+ if (devpriv->count < num_points && cmd->stop_src == TRIG_COUNT)
+ num_points = devpriv->count;
+
+ /* figure out how many points will be stored next time */
+ leftover = 0;
+ if (cmd->stop_src == TRIG_NONE) {
+ leftover = comedi_bytes_to_samples(s, desc->size);
+ } else if (devpriv->count > max_points) {
+ leftover = devpriv->count - max_points;
+ if (leftover > max_points)
+ leftover = max_points;
+ }
+ /* there should only be a residue if collection was stopped by having
+ * the stop_src set to an external trigger, in which case there
+ * will be no more data
+ */
+ if (residue)
+ leftover = 0;
+
+ for (i = 0; i < num_points; i++) {
+ /* write data point to comedi buffer */
+ dpnt = buf[i];
+ /* convert from 2's complement to unsigned coding */
+ dpnt ^= 0x8000;
+ comedi_buf_write_samples(s, &dpnt, 1);
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (--devpriv->count == 0) { /* end of acquisition */
+ async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+ }
+ /* re-enable dma */
+ if (leftover) {
+ desc->size = comedi_samples_to_bytes(s, leftover);
+ comedi_isadma_program(desc);
+ }
+
+ comedi_handle_events(dev, s);
+
+ /* clear interrupt */
+ outw(0x00, dev->iobase + DMA_TC_CLEAR_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int a2150_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct a2150_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[0];
+
+ /* disable dma on card */
+ devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT;
+ outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG);
+
+ /* disable computer's dma */
+ comedi_isadma_disable(desc->chan);
+
+ /* clear fifo and reset triggering circuitry */
+ outw(0, dev->iobase + FIFO_RESET_REG);
+
+ return 0;
+}
+
+/*
+ * sets bits in devpriv->clock_bits to nearest approximation of requested
+ * period, adjusts requested period to actual timing.
+ */
+static int a2150_get_timing(struct comedi_device *dev, unsigned int *period,
+ unsigned int flags)
+{
+ const struct a2150_board *thisboard = dev->board_ptr;
+ struct a2150_private *devpriv = dev->private;
+ int lub, glb, temp;
+ int lub_divisor_shift, lub_index, glb_divisor_shift, glb_index;
+ int i, j;
+
+ /* initialize greatest lower and least upper bounds */
+ lub_divisor_shift = 3;
+ lub_index = 0;
+ lub = thisboard->clock[lub_index] * (1 << lub_divisor_shift);
+ glb_divisor_shift = 0;
+ glb_index = thisboard->num_clocks - 1;
+ glb = thisboard->clock[glb_index] * (1 << glb_divisor_shift);
+
+ /* make sure period is in available range */
+ if (*period < glb)
+ *period = glb;
+ if (*period > lub)
+ *period = lub;
+
+ /* we can multiply period by 1, 2, 4, or 8, using (1 << i) */
+ for (i = 0; i < 4; i++) {
+ /* there are a maximum of 4 master clocks */
+ for (j = 0; j < thisboard->num_clocks; j++) {
+ /* temp is the period in nanosec we are evaluating */
+ temp = thisboard->clock[j] * (1 << i);
+ /* if it is the best match yet */
+ if (temp < lub && temp >= *period) {
+ lub_divisor_shift = i;
+ lub_index = j;
+ lub = temp;
+ }
+ if (temp > glb && temp <= *period) {
+ glb_divisor_shift = i;
+ glb_index = j;
+ glb = temp;
+ }
+ }
+ }
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ /* if least upper bound is better approximation */
+ if (lub - *period < *period - glb)
+ *period = lub;
+ else
+ *period = glb;
+ break;
+ case CMDF_ROUND_UP:
+ *period = lub;
+ break;
+ case CMDF_ROUND_DOWN:
+ *period = glb;
+ break;
+ }
+
+ /* set clock bits for config register appropriately */
+ devpriv->config_bits &= ~CLOCK_MASK;
+ if (*period == lub) {
+ devpriv->config_bits |=
+ CLOCK_SELECT_BITS(lub_index) |
+ CLOCK_DIVISOR_BITS(lub_divisor_shift);
+ } else {
+ devpriv->config_bits |=
+ CLOCK_SELECT_BITS(glb_index) |
+ CLOCK_DIVISOR_BITS(glb_divisor_shift);
+ }
+
+ return 0;
+}
+
+static int a2150_set_chanlist(struct comedi_device *dev,
+ unsigned int start_channel,
+ unsigned int num_channels)
+{
+ struct a2150_private *devpriv = dev->private;
+
+ if (start_channel + num_channels > 4)
+ return -1;
+
+ devpriv->config_bits &= ~CHANNEL_MASK;
+
+ switch (num_channels) {
+ case 1:
+ devpriv->config_bits |= CHANNEL_BITS(0x4 | start_channel);
+ break;
+ case 2:
+ if (start_channel == 0)
+ devpriv->config_bits |= CHANNEL_BITS(0x2);
+ else if (start_channel == 2)
+ devpriv->config_bits |= CHANNEL_BITS(0x3);
+ else
+ return -1;
+ break;
+ case 4:
+ devpriv->config_bits |= CHANNEL_BITS(0x1);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static int a2150_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ if (cmd->chanlist_len == 2 && (chan0 == 1 || chan0 == 3)) {
+ dev_dbg(dev->class_dev,
+ "length 2 chanlist must be channels 0,1 or channels 2,3\n");
+ return -EINVAL;
+ }
+
+ if (cmd->chanlist_len == 3) {
+ dev_dbg(dev->class_dev,
+ "chanlist must have 1,2 or 4 channels\n");
+ return -EINVAL;
+ }
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i)) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (chan == 2)
+ aref0 = aref;
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "channels 0/1 and 2/3 must have the same analog reference\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int a2150_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct a2150_board *thisboard = dev->board_ptr;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ a2150_get_timing(dev, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= a2150_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int a2150_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct a2150_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[0];
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int old_config_bits = devpriv->config_bits;
+ unsigned int trigger_bits;
+
+ if (cmd->flags & CMDF_PRIORITY) {
+ dev_err(dev->class_dev,
+ "dma incompatible with hard real-time interrupt (CMDF_PRIORITY), aborting\n");
+ return -1;
+ }
+ /* clear fifo and reset triggering circuitry */
+ outw(0, dev->iobase + FIFO_RESET_REG);
+
+ /* setup chanlist */
+ if (a2150_set_chanlist(dev, CR_CHAN(cmd->chanlist[0]),
+ cmd->chanlist_len) < 0)
+ return -1;
+
+ /* setup ac/dc coupling */
+ if (CR_AREF(cmd->chanlist[0]) == AREF_OTHER)
+ devpriv->config_bits |= AC0_BIT;
+ else
+ devpriv->config_bits &= ~AC0_BIT;
+ if (CR_AREF(cmd->chanlist[2]) == AREF_OTHER)
+ devpriv->config_bits |= AC1_BIT;
+ else
+ devpriv->config_bits &= ~AC1_BIT;
+
+ /* setup timing */
+ a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags);
+
+ /* send timing, channel, config bits */
+ outw(devpriv->config_bits, dev->iobase + CONFIG_REG);
+
+ /* initialize number of samples remaining */
+ devpriv->count = cmd->stop_arg * cmd->chanlist_len;
+
+ comedi_isadma_disable(desc->chan);
+
+ /* set size of transfer to fill in 1/3 second */
+#define ONE_THIRD_SECOND 333333333
+ desc->size = comedi_bytes_per_sample(s) * cmd->chanlist_len *
+ ONE_THIRD_SECOND / cmd->scan_begin_arg;
+ if (desc->size > desc->maxsize)
+ desc->size = desc->maxsize;
+ if (desc->size < comedi_bytes_per_sample(s))
+ desc->size = comedi_bytes_per_sample(s);
+ desc->size -= desc->size % comedi_bytes_per_sample(s);
+
+ comedi_isadma_program(desc);
+
+ /* clear dma interrupt before enabling it, to try and get rid of that
+ * one spurious interrupt that has been happening */
+ outw(0x00, dev->iobase + DMA_TC_CLEAR_REG);
+
+ /* enable dma on card */
+ devpriv->irq_dma_bits |= DMA_INTR_EN_BIT | DMA_EN_BIT;
+ outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG);
+
+ /* may need to wait 72 sampling periods if timing was changed */
+ comedi_8254_load(dev->pacer, 2, 72, I8254_MODE0 | I8254_BINARY);
+
+ /* setup start triggering */
+ trigger_bits = 0;
+ /* decide if we need to wait 72 periods for valid data */
+ if (cmd->start_src == TRIG_NOW &&
+ (old_config_bits & CLOCK_MASK) !=
+ (devpriv->config_bits & CLOCK_MASK)) {
+ /* set trigger source to delay trigger */
+ trigger_bits |= DELAY_TRIGGER_BITS;
+ } else {
+ /* otherwise no delay */
+ trigger_bits |= POST_TRIGGER_BITS;
+ }
+ /* enable external hardware trigger */
+ if (cmd->start_src == TRIG_EXT) {
+ trigger_bits |= HW_TRIG_EN;
+ } else if (cmd->start_src == TRIG_OTHER) {
+ /* XXX add support for level/slope start trigger using TRIG_OTHER */
+ dev_err(dev->class_dev, "you shouldn't see this?\n");
+ }
+ /* send trigger config bits */
+ outw(trigger_bits, dev->iobase + TRIGGER_REG);
+
+ /* start acquisition for soft trigger */
+ if (cmd->start_src == TRIG_NOW)
+ outw(0, dev->iobase + FIFO_START_REG);
+
+ return 0;
+}
+
+static int a2150_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + STATUS_REG);
+ if (status & FNE_BIT)
+ return 0;
+ return -EBUSY;
+}
+
+static int a2150_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct a2150_private *devpriv = dev->private;
+ unsigned int n;
+ int ret;
+
+ /* clear fifo and reset triggering circuitry */
+ outw(0, dev->iobase + FIFO_RESET_REG);
+
+ /* setup chanlist */
+ if (a2150_set_chanlist(dev, CR_CHAN(insn->chanspec), 1) < 0)
+ return -1;
+
+ /* set dc coupling */
+ devpriv->config_bits &= ~AC0_BIT;
+ devpriv->config_bits &= ~AC1_BIT;
+
+ /* send timing, channel, config bits */
+ outw(devpriv->config_bits, dev->iobase + CONFIG_REG);
+
+ /* disable dma on card */
+ devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT;
+ outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG);
+
+ /* setup start triggering */
+ outw(0, dev->iobase + TRIGGER_REG);
+
+ /* start acquisition for soft trigger */
+ outw(0, dev->iobase + FIFO_START_REG);
+
+ /*
+ * there is a 35.6 sample delay for data to get through the
+ * antialias filter
+ */
+ for (n = 0; n < 36; n++) {
+ ret = comedi_timeout(dev, s, insn, a2150_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ inw(dev->iobase + FIFO_DATA_REG);
+ }
+
+ /* read data */
+ for (n = 0; n < insn->n; n++) {
+ ret = comedi_timeout(dev, s, insn, a2150_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[n] = inw(dev->iobase + FIFO_DATA_REG);
+ data[n] ^= 0x8000;
+ }
+
+ /* clear fifo and reset triggering circuitry */
+ outw(0, dev->iobase + FIFO_RESET_REG);
+
+ return n;
+}
+
+static void a2150_alloc_irq_and_dma(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct a2150_private *devpriv = dev->private;
+ unsigned int irq_num = it->options[1];
+ unsigned int dma_chan = it->options[2];
+
+ /*
+ * Only IRQs 15, 14, 12-9, and 7-3 are valid.
+ * Only DMA channels 7-5 and 3-0 are valid.
+ */
+ if (irq_num > 15 || dma_chan > 7 ||
+ !((1 << irq_num) & 0xdef8) || !((1 << dma_chan) & 0xef))
+ return;
+
+ if (request_irq(irq_num, a2150_interrupt, 0, dev->board_name, dev))
+ return;
+
+ /* DMA uses 1 buffer */
+ devpriv->dma = comedi_isadma_alloc(dev, 1, dma_chan, dma_chan,
+ A2150_DMA_BUFFER_SIZE,
+ COMEDI_ISADMA_READ);
+ if (!devpriv->dma) {
+ free_irq(irq_num, dev);
+ } else {
+ dev->irq = irq_num;
+ devpriv->irq_dma_bits = IRQ_LVL_BITS(irq_num) |
+ DMA_CHAN_BITS(dma_chan);
+ }
+}
+
+static void a2150_free_dma(struct comedi_device *dev)
+{
+ struct a2150_private *devpriv = dev->private;
+
+ if (devpriv)
+ comedi_isadma_free(devpriv->dma);
+}
+
+/* probes board type, returns offset */
+static int a2150_probe(struct comedi_device *dev)
+{
+ int status = inw(dev->iobase + STATUS_REG);
+
+ return ID_BITS(status);
+}
+
+static int a2150_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct a2150_board *thisboard;
+ struct a2150_private *devpriv;
+ struct comedi_subdevice *s;
+ static const int timeout = 2000;
+ int i;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x1c);
+ if (ret)
+ return ret;
+
+ i = a2150_probe(dev);
+ if (i >= ARRAY_SIZE(a2150_boards))
+ return -ENODEV;
+
+ dev->board_ptr = a2150_boards + i;
+ thisboard = dev->board_ptr;
+ dev->board_name = thisboard->name;
+
+ /* an IRQ and DMA are required to support async commands */
+ a2150_alloc_irq_and_dma(dev, it);
+
+ dev->pacer = comedi_8254_init(dev->iobase + I8253_BASE_REG,
+ 0, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_OTHER;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->range_table = &range_a2150;
+ s->insn_read = a2150_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmd = a2150_ai_cmd;
+ s->do_cmdtest = a2150_ai_cmdtest;
+ s->cancel = a2150_cancel;
+ }
+
+ /* set card's irq and dma levels */
+ outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG);
+
+ /* reset and sync adc clock circuitry */
+ outw_p(DPD_BIT | APD_BIT, dev->iobase + CONFIG_REG);
+ outw_p(DPD_BIT, dev->iobase + CONFIG_REG);
+ /* initialize configuration register */
+ devpriv->config_bits = 0;
+ outw(devpriv->config_bits, dev->iobase + CONFIG_REG);
+ /* wait until offset calibration is done, then enable analog inputs */
+ for (i = 0; i < timeout; i++) {
+ if ((DCAL_BIT & inw(dev->iobase + STATUS_REG)) == 0)
+ break;
+ udelay(1000);
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev,
+ "timed out waiting for offset calibration to complete\n");
+ return -ETIME;
+ }
+ devpriv->config_bits |= ENABLE0_BIT | ENABLE1_BIT;
+ outw(devpriv->config_bits, dev->iobase + CONFIG_REG);
+
+ return 0;
+};
+
+static void a2150_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ outw(APD_BIT | DPD_BIT, dev->iobase + CONFIG_REG);
+ a2150_free_dma(dev);
+ comedi_legacy_detach(dev);
+};
+
+static struct comedi_driver ni_at_a2150_driver = {
+ .driver_name = "ni_at_a2150",
+ .module = THIS_MODULE,
+ .attach = a2150_attach,
+ .detach = a2150_detach,
+};
+module_comedi_driver(ni_at_a2150_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_at_ao.c b/drivers/staging/comedi/drivers/ni_at_ao.c
new file mode 100644
index 000000000..f27aa0e82
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_at_ao.c
@@ -0,0 +1,383 @@
+/*
+ * ni_at_ao.c
+ * Driver for NI AT-AO-6/10 boards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000,2002 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_at_ao
+ * Description: National Instruments AT-AO-6/10
+ * Devices: [National Instruments] AT-AO-6 (at-ao-6), AT-AO-10 (at-ao-10)
+ * Status: should work
+ * Author: David A. Schleef <ds@schleef.org>
+ * Updated: Sun Dec 26 12:26:28 EST 2004
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ * [1] - IRQ (unused)
+ * [2] - DMA (unused)
+ * [3] - analog output range, set by jumpers on hardware
+ * 0 for -10 to 10V bipolar
+ * 1 for 0V to 10V unipolar
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+#include "comedi_8254.h"
+
+/*
+ * Register map
+ *
+ * Register-level programming information can be found in NI
+ * document 320379.pdf.
+ */
+#define ATAO_DIO_REG 0x00
+#define ATAO_CFG2_REG 0x02
+#define ATAO_CFG2_CALLD_NOP (0 << 14)
+#define ATAO_CFG2_CALLD(x) ((((x) >> 3) + 1) << 14)
+#define ATAO_CFG2_FFRTEN (1 << 13)
+#define ATAO_CFG2_DACS(x) (1 << (((x) / 2) + 8))
+#define ATAO_CFG2_LDAC(x) (1 << (((x) / 2) + 3))
+#define ATAO_CFG2_PROMEN (1 << 2)
+#define ATAO_CFG2_SCLK (1 << 1)
+#define ATAO_CFG2_SDATA (1 << 0)
+#define ATAO_CFG3_REG 0x04
+#define ATAO_CFG3_DMAMODE (1 << 6)
+#define ATAO_CFG3_CLKOUT (1 << 5)
+#define ATAO_CFG3_RCLKEN (1 << 4)
+#define ATAO_CFG3_DOUTEN2 (1 << 3)
+#define ATAO_CFG3_DOUTEN1 (1 << 2)
+#define ATAO_CFG3_EN2_5V (1 << 1)
+#define ATAO_CFG3_SCANEN (1 << 0)
+#define ATAO_82C53_BASE 0x06
+#define ATAO_CFG1_REG 0x0a
+#define ATAO_CFG1_EXTINT2EN (1 << 15)
+#define ATAO_CFG1_EXTINT1EN (1 << 14)
+#define ATAO_CFG1_CNTINT2EN (1 << 13)
+#define ATAO_CFG1_CNTINT1EN (1 << 12)
+#define ATAO_CFG1_TCINTEN (1 << 11)
+#define ATAO_CFG1_CNT1SRC (1 << 10)
+#define ATAO_CFG1_CNT2SRC (1 << 9)
+#define ATAO_CFG1_FIFOEN (1 << 8)
+#define ATAO_CFG1_GRP2WR (1 << 7)
+#define ATAO_CFG1_EXTUPDEN (1 << 6)
+#define ATAO_CFG1_DMARQ (1 << 5)
+#define ATAO_CFG1_DMAEN (1 << 4)
+#define ATAO_CFG1_CH(x) (((x) & 0xf) << 0)
+#define ATAO_STATUS_REG 0x0a
+#define ATAO_STATUS_FH (1 << 6)
+#define ATAO_STATUS_FE (1 << 5)
+#define ATAO_STATUS_FF (1 << 4)
+#define ATAO_STATUS_INT2 (1 << 3)
+#define ATAO_STATUS_INT1 (1 << 2)
+#define ATAO_STATUS_TCINT (1 << 1)
+#define ATAO_STATUS_PROMOUT (1 << 0)
+#define ATAO_FIFO_WRITE_REG 0x0c
+#define ATAO_FIFO_CLEAR_REG 0x0c
+#define ATAO_AO_REG(x) (0x0c + ((x) * 2))
+
+/* registers with _2_ are accessed when GRP2WR is set in CFG1 */
+#define ATAO_2_DMATCCLR_REG 0x00
+#define ATAO_2_INT1CLR_REG 0x02
+#define ATAO_2_INT2CLR_REG 0x04
+#define ATAO_2_RTSISHFT_REG 0x06
+#define ATAO_2_RTSISHFT_RSI (1 << 0)
+#define ATAO_2_RTSISTRB_REG 0x07
+
+struct atao_board {
+ const char *name;
+ int n_ao_chans;
+};
+
+static const struct atao_board atao_boards[] = {
+ {
+ .name = "at-ao-6",
+ .n_ao_chans = 6,
+ }, {
+ .name = "at-ao-10",
+ .n_ao_chans = 10,
+ },
+};
+
+struct atao_private {
+ unsigned short cfg1;
+ unsigned short cfg3;
+
+ /* Used for caldac readback */
+ unsigned char caldac[21];
+};
+
+static void atao_select_reg_group(struct comedi_device *dev, int group)
+{
+ struct atao_private *devpriv = dev->private;
+
+ if (group)
+ devpriv->cfg1 |= ATAO_CFG1_GRP2WR;
+ else
+ devpriv->cfg1 &= ~ATAO_CFG1_GRP2WR;
+ outw(devpriv->cfg1, dev->iobase + ATAO_CFG1_REG);
+}
+
+static int atao_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ if (chan == 0)
+ atao_select_reg_group(dev, 1);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* the hardware expects two's complement values */
+ outw(comedi_offset_munge(s, val),
+ dev->iobase + ATAO_AO_REG(chan));
+ }
+ s->readback[chan] = val;
+
+ if (chan == 0)
+ atao_select_reg_group(dev, 0);
+
+ return insn->n;
+}
+
+static int atao_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + ATAO_DIO_REG);
+
+ data[1] = inw(dev->iobase + ATAO_DIO_REG);
+
+ return insn->n;
+}
+
+static int atao_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atao_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ if (s->io_bits & 0x0f)
+ devpriv->cfg3 |= ATAO_CFG3_DOUTEN1;
+ else
+ devpriv->cfg3 &= ~ATAO_CFG3_DOUTEN1;
+ if (s->io_bits & 0xf0)
+ devpriv->cfg3 |= ATAO_CFG3_DOUTEN2;
+ else
+ devpriv->cfg3 &= ~ATAO_CFG3_DOUTEN2;
+
+ outw(devpriv->cfg3, dev->iobase + ATAO_CFG3_REG);
+
+ return insn->n;
+}
+
+/*
+ * There are three DAC8800 TrimDACs on the board. These are 8-channel,
+ * 8-bit DACs that are used to calibrate the Analog Output channels.
+ * The factory default calibration values are stored in the EEPROM.
+ * The TrimDACs, and EEPROM addresses, are mapped as:
+ *
+ * Channel EEPROM Description
+ * ----------------- ------ -----------------------------------
+ * 0 - DAC0 Chan 0 0x30 AO Channel 0 Offset
+ * 1 - DAC0 Chan 1 0x31 AO Channel 0 Gain
+ * 2 - DAC0 Chan 2 0x32 AO Channel 1 Offset
+ * 3 - DAC0 Chan 3 0x33 AO Channel 1 Gain
+ * 4 - DAC0 Chan 4 0x34 AO Channel 2 Offset
+ * 5 - DAC0 Chan 5 0x35 AO Channel 2 Gain
+ * 6 - DAC0 Chan 6 0x36 AO Channel 3 Offset
+ * 7 - DAC0 Chan 7 0x37 AO Channel 3 Gain
+ * 8 - DAC1 Chan 0 0x38 AO Channel 4 Offset
+ * 9 - DAC1 Chan 1 0x39 AO Channel 4 Gain
+ * 10 - DAC1 Chan 2 0x3a AO Channel 5 Offset
+ * 11 - DAC1 Chan 3 0x3b AO Channel 5 Gain
+ * 12 - DAC1 Chan 4 0x3c 2.5V Offset
+ * 13 - DAC1 Chan 5 0x3d AO Channel 6 Offset (at-ao-10 only)
+ * 14 - DAC1 Chan 6 0x3e AO Channel 6 Gain (at-ao-10 only)
+ * 15 - DAC1 Chan 7 0x3f AO Channel 7 Offset (at-ao-10 only)
+ * 16 - DAC2 Chan 0 0x40 AO Channel 7 Gain (at-ao-10 only)
+ * 17 - DAC2 Chan 1 0x41 AO Channel 8 Offset (at-ao-10 only)
+ * 18 - DAC2 Chan 2 0x42 AO Channel 8 Gain (at-ao-10 only)
+ * 19 - DAC2 Chan 3 0x43 AO Channel 9 Offset (at-ao-10 only)
+ * 20 - DAC2 Chan 4 0x44 AO Channel 9 Gain (at-ao-10 only)
+ * DAC2 Chan 5 0x45 Reserved
+ * DAC2 Chan 6 0x46 Reserved
+ * DAC2 Chan 7 0x47 Reserved
+ */
+static int atao_calib_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ if (insn->n) {
+ unsigned int val = data[insn->n - 1];
+ unsigned int bitstring = ((chan & 0x7) << 8) | val;
+ unsigned int bits;
+ int bit;
+
+ /* write the channel and last data value to the caldac */
+ /* clock the bitstring to the caldac; MSB -> LSB */
+ for (bit = 1 << 10; bit; bit >>= 1) {
+ bits = (bit & bitstring) ? ATAO_CFG2_SDATA : 0;
+
+ outw(bits, dev->iobase + ATAO_CFG2_REG);
+ outw(bits | ATAO_CFG2_SCLK,
+ dev->iobase + ATAO_CFG2_REG);
+ }
+
+ /* strobe the caldac to load the value */
+ outw(ATAO_CFG2_CALLD(chan), dev->iobase + ATAO_CFG2_REG);
+ outw(ATAO_CFG2_CALLD_NOP, dev->iobase + ATAO_CFG2_REG);
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static void atao_reset(struct comedi_device *dev)
+{
+ struct atao_private *devpriv = dev->private;
+
+ /* This is the reset sequence described in the manual */
+
+ devpriv->cfg1 = 0;
+ outw(devpriv->cfg1, dev->iobase + ATAO_CFG1_REG);
+
+ /* Put outputs of counter 1 and counter 2 in a high state */
+ comedi_8254_set_mode(dev->pacer, 0, I8254_MODE4 | I8254_BINARY);
+ comedi_8254_set_mode(dev->pacer, 1, I8254_MODE4 | I8254_BINARY);
+ comedi_8254_write(dev->pacer, 0, 0x0003);
+
+ outw(ATAO_CFG2_CALLD_NOP, dev->iobase + ATAO_CFG2_REG);
+
+ devpriv->cfg3 = 0;
+ outw(devpriv->cfg3, dev->iobase + ATAO_CFG3_REG);
+
+ inw(dev->iobase + ATAO_FIFO_CLEAR_REG);
+
+ atao_select_reg_group(dev, 1);
+ outw(0, dev->iobase + ATAO_2_INT1CLR_REG);
+ outw(0, dev->iobase + ATAO_2_INT2CLR_REG);
+ outw(0, dev->iobase + ATAO_2_DMATCCLR_REG);
+ atao_select_reg_group(dev, 0);
+}
+
+static int atao_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct atao_board *board = dev->board_ptr;
+ struct atao_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x20);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->pacer = comedi_8254_init(dev->iobase + ATAO_82C53_BASE,
+ 0, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_ao_chans;
+ s->maxdata = 0x0fff;
+ s->range_table = it->options[3] ? &range_unipolar10 : &range_bipolar10;
+ s->insn_write = atao_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = atao_dio_insn_bits;
+ s->insn_config = atao_dio_insn_config;
+
+ /* caldac subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = (board->n_ao_chans * 2) + 1;
+ s->maxdata = 0xff;
+ s->insn_write = atao_calib_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* EEPROM subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_UNUSED;
+
+ atao_reset(dev);
+
+ return 0;
+}
+
+static struct comedi_driver ni_at_ao_driver = {
+ .driver_name = "ni_at_ao",
+ .module = THIS_MODULE,
+ .attach = atao_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &atao_boards[0].name,
+ .offset = sizeof(struct atao_board),
+ .num_names = ARRAY_SIZE(atao_boards),
+};
+module_comedi_driver(ni_at_ao_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for NI AT-AO-6/10 boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_atmio.c b/drivers/staging/comedi/drivers/ni_atmio.c
new file mode 100644
index 000000000..1304b0698
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_atmio.c
@@ -0,0 +1,378 @@
+/*
+ comedi/drivers/ni_atmio.c
+ Hardware driver for NI AT-MIO E series cards
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2001 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_atmio
+Description: National Instruments AT-MIO-E series
+Author: ds
+Devices: [National Instruments] AT-MIO-16E-1 (ni_atmio),
+ AT-MIO-16E-2, AT-MIO-16E-10, AT-MIO-16DE-10, AT-MIO-64E-3,
+ AT-MIO-16XE-50, AT-MIO-16XE-10, AT-AI-16XE-10
+Status: works
+Updated: Thu May 1 20:03:02 CDT 2003
+
+The driver has 2.6 kernel isapnp support, and
+will automatically probe for a supported board if the
+I/O base is left unspecified with comedi_config.
+However, many of
+the isapnp id numbers are unknown. If your board is not
+recognized, please send the output of 'cat /proc/isapnp'
+(you may need to modprobe the isa-pnp module for
+/proc/isapnp to exist) so the
+id numbers for your board can be added to the driver.
+
+Otherwise, you can use the isapnptools package to configure
+your board. Use isapnp to
+configure the I/O base and IRQ for the board, and then pass
+the same values as
+parameters in comedi_config. A sample isapnp.conf file is included
+in the etc/ directory of Comedilib.
+
+Comedilib includes a utility to autocalibrate these boards. The
+boards seem to boot into a state where the all calibration DACs
+are at one extreme of their range, thus the default calibration
+is terrible. Calibration at boot is strongly encouraged.
+
+To use the extended digital I/O on some of the boards, enable the
+8255 driver when configuring the Comedi source tree.
+
+External triggering is supported for some events. The channel index
+(scan_begin_arg, etc.) maps to PFI0 - PFI9.
+
+Some of the more esoteric triggering possibilities of these boards
+are not supported.
+*/
+/*
+ The real guts of the driver is in ni_mio_common.c, which is included
+ both here and in ni_pcimio.c
+
+ Interrupt support added by Truxton Fulton <trux@truxton.com>
+
+ References for specifications:
+
+ 340747b.pdf Register Level Programmer Manual (obsolete)
+ 340747c.pdf Register Level Programmer Manual (new)
+ DAQ-STC reference manual
+
+ Other possibly relevant info:
+
+ 320517c.pdf User manual (obsolete)
+ 320517f.pdf User manual (new)
+ 320889a.pdf delete
+ 320906c.pdf maximum signal ratings
+ 321066a.pdf about 16x
+ 321791a.pdf discontinuation of at-mio-16e-10 rev. c
+ 321808a.pdf about at-mio-16e-10 rev P
+ 321837a.pdf discontinuation of at-mio-16de-10 rev d
+ 321838a.pdf about at-mio-16de-10 rev N
+
+ ISSUES:
+
+ need to deal with external reference for DAC, and other DAC
+ properties in board properties
+
+ deal with at-mio-16de-10 revision D to N changes, etc.
+
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include <linux/isapnp.h>
+
+#include "ni_stc.h"
+#include "8255.h"
+
+/*
+ * AT specific setup
+ */
+
+static const struct ni_board_struct ni_boards[] = {
+ {
+ .name = "at-mio-16e-1",
+ .device_id = 44,
+ .isapnp_id = 0x0000, /* XXX unknown */
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { mb88341 },
+ }, {
+ .name = "at-mio-16e-2",
+ .device_id = 25,
+ .isapnp_id = 0x1900,
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 2048,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 2000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { mb88341 },
+ }, {
+ .name = "at-mio-16e-10",
+ .device_id = 36,
+ .isapnp_id = 0x2400,
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 10000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 10000,
+ .caldac = { ad8804_debug },
+ }, {
+ .name = "at-mio-16de-10",
+ .device_id = 37,
+ .isapnp_id = 0x2500,
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 10000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 10000,
+ .caldac = { ad8804_debug },
+ .has_8255 = 1,
+ }, {
+ .name = "at-mio-64e-3",
+ .device_id = 38,
+ .isapnp_id = 0x2600,
+ .n_adchan = 64,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 2048,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 2000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { ad8804_debug },
+ }, {
+ .name = "at-mio-16xe-50",
+ .device_id = 39,
+ .isapnp_id = 0x2700,
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_8,
+ .ai_speed = 50000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 50000,
+ .caldac = { dac8800, dac8043 },
+ }, {
+ .name = "at-mio-16xe-10",
+ .device_id = 50,
+ .isapnp_id = 0x0000, /* XXX unknown */
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ }, {
+ .name = "at-ai-16xe-10",
+ .device_id = 51,
+ .isapnp_id = 0x0000, /* XXX unknown */
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1, /* unknown */
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+};
+
+static const int ni_irqpin[] = {
+ -1, -1, -1, 0, 1, 2, -1, 3, -1, -1, 4, 5, 6, -1, -1, 7
+};
+
+#include "ni_mio_common.c"
+
+static struct pnp_device_id device_ids[] = {
+ {.id = "NIC1900", .driver_data = 0},
+ {.id = "NIC2400", .driver_data = 0},
+ {.id = "NIC2500", .driver_data = 0},
+ {.id = "NIC2600", .driver_data = 0},
+ {.id = "NIC2700", .driver_data = 0},
+ {.id = ""}
+};
+
+MODULE_DEVICE_TABLE(pnp, device_ids);
+
+static int ni_isapnp_find_board(struct pnp_dev **dev)
+{
+ struct pnp_dev *isapnp_dev = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ni_boards); i++) {
+ isapnp_dev = pnp_find_dev(NULL,
+ ISAPNP_VENDOR('N', 'I', 'C'),
+ ISAPNP_FUNCTION(ni_boards[i].
+ isapnp_id), NULL);
+
+ if (!isapnp_dev || !isapnp_dev->card)
+ continue;
+
+ if (pnp_device_attach(isapnp_dev) < 0)
+ continue;
+
+ if (pnp_activate_dev(isapnp_dev) < 0) {
+ pnp_device_detach(isapnp_dev);
+ return -EAGAIN;
+ }
+
+ if (!pnp_port_valid(isapnp_dev, 0) ||
+ !pnp_irq_valid(isapnp_dev, 0)) {
+ pnp_device_detach(isapnp_dev);
+ return -ENOMEM;
+ }
+ break;
+ }
+ if (i == ARRAY_SIZE(ni_boards))
+ return -ENODEV;
+ *dev = isapnp_dev;
+ return 0;
+}
+
+static int ni_getboardtype(struct comedi_device *dev)
+{
+ int device_id = ni_read_eeprom(dev, 511);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ni_boards); i++) {
+ if (ni_boards[i].device_id == device_id)
+ return i;
+ }
+ if (device_id == 255)
+ dev_err(dev->class_dev, "can't find board\n");
+ else if (device_id == 0)
+ dev_err(dev->class_dev,
+ "EEPROM read error (?) or device not found\n");
+ else
+ dev_err(dev->class_dev,
+ "unknown device ID %d -- contact author\n", device_id);
+
+ return -1;
+}
+
+static int ni_atmio_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct ni_board_struct *boardtype;
+ struct pnp_dev *isapnp_dev;
+ int ret;
+ unsigned long iobase;
+ int board;
+ unsigned int irq;
+
+ ret = ni_alloc_private(dev);
+ if (ret)
+ return ret;
+
+ iobase = it->options[0];
+ irq = it->options[1];
+ isapnp_dev = NULL;
+ if (iobase == 0) {
+ ret = ni_isapnp_find_board(&isapnp_dev);
+ if (ret < 0)
+ return ret;
+
+ iobase = pnp_port_start(isapnp_dev, 0);
+ irq = pnp_irq(isapnp_dev, 0);
+ comedi_set_hw_dev(dev, &isapnp_dev->dev);
+ }
+
+ ret = comedi_request_region(dev, iobase, 0x20);
+ if (ret)
+ return ret;
+
+ /* get board type */
+
+ board = ni_getboardtype(dev);
+ if (board < 0)
+ return -EIO;
+
+ dev->board_ptr = ni_boards + board;
+ boardtype = dev->board_ptr;
+ dev->board_name = boardtype->name;
+
+ /* irq stuff */
+
+ if (irq != 0) {
+ if (irq > 15 || ni_irqpin[irq] == -1)
+ return -EINVAL;
+ ret = request_irq(irq, ni_E_interrupt, 0,
+ dev->board_name, dev);
+ if (ret < 0)
+ return -EINVAL;
+ dev->irq = irq;
+ }
+
+ /* generic E series stuff in ni_mio_common.c */
+
+ ret = ni_E_init(dev, ni_irqpin[dev->irq], 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void ni_atmio_detach(struct comedi_device *dev)
+{
+ struct pnp_dev *isapnp_dev;
+
+ mio_common_detach(dev);
+ comedi_legacy_detach(dev);
+
+ isapnp_dev = dev->hw_dev ? to_pnp_dev(dev->hw_dev) : NULL;
+ if (isapnp_dev)
+ pnp_device_detach(isapnp_dev);
+}
+
+static struct comedi_driver ni_atmio_driver = {
+ .driver_name = "ni_atmio",
+ .module = THIS_MODULE,
+ .attach = ni_atmio_attach,
+ .detach = ni_atmio_detach,
+};
+module_comedi_driver(ni_atmio_driver);
diff --git a/drivers/staging/comedi/drivers/ni_atmio16d.c b/drivers/staging/comedi/drivers/ni_atmio16d.c
new file mode 100644
index 000000000..c3eb54622
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_atmio16d.c
@@ -0,0 +1,760 @@
+/*
+ comedi/drivers/ni_atmio16d.c
+ Hardware driver for National Instruments AT-MIO16D board
+ Copyright (C) 2000 Chris R. Baugher <baugher@enteract.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+/*
+Driver: ni_atmio16d
+Description: National Instruments AT-MIO-16D
+Author: Chris R. Baugher <baugher@enteract.com>
+Status: unknown
+Devices: [National Instruments] AT-MIO-16 (atmio16), AT-MIO-16D (atmio16d)
+*/
+/*
+ * I must give credit here to Michal Dobes <dobes@tesnet.cz> who
+ * wrote the driver for Advantec's pcl812 boards. I used the interrupt
+ * handling code from his driver as an example for this one.
+ *
+ * Chris Baugher
+ * 5/1/2000
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+
+/* Configuration and Status Registers */
+#define COM_REG_1 0x00 /* wo 16 */
+#define STAT_REG 0x00 /* ro 16 */
+#define COM_REG_2 0x02 /* wo 16 */
+/* Event Strobe Registers */
+#define START_CONVERT_REG 0x08 /* wo 16 */
+#define START_DAQ_REG 0x0A /* wo 16 */
+#define AD_CLEAR_REG 0x0C /* wo 16 */
+#define EXT_STROBE_REG 0x0E /* wo 16 */
+/* Analog Output Registers */
+#define DAC0_REG 0x10 /* wo 16 */
+#define DAC1_REG 0x12 /* wo 16 */
+#define INT2CLR_REG 0x14 /* wo 16 */
+/* Analog Input Registers */
+#define MUX_CNTR_REG 0x04 /* wo 16 */
+#define MUX_GAIN_REG 0x06 /* wo 16 */
+#define AD_FIFO_REG 0x16 /* ro 16 */
+#define DMA_TC_INT_CLR_REG 0x16 /* wo 16 */
+/* AM9513A Counter/Timer Registers */
+#define AM9513A_DATA_REG 0x18 /* rw 16 */
+#define AM9513A_COM_REG 0x1A /* wo 16 */
+#define AM9513A_STAT_REG 0x1A /* ro 16 */
+/* MIO-16 Digital I/O Registers */
+#define MIO_16_DIG_IN_REG 0x1C /* ro 16 */
+#define MIO_16_DIG_OUT_REG 0x1C /* wo 16 */
+/* RTSI Switch Registers */
+#define RTSI_SW_SHIFT_REG 0x1E /* wo 8 */
+#define RTSI_SW_STROBE_REG 0x1F /* wo 8 */
+/* DIO-24 Registers */
+#define DIO_24_PORTA_REG 0x00 /* rw 8 */
+#define DIO_24_PORTB_REG 0x01 /* rw 8 */
+#define DIO_24_PORTC_REG 0x02 /* rw 8 */
+#define DIO_24_CNFG_REG 0x03 /* wo 8 */
+
+/* Command Register bits */
+#define COMREG1_2SCADC 0x0001
+#define COMREG1_1632CNT 0x0002
+#define COMREG1_SCANEN 0x0008
+#define COMREG1_DAQEN 0x0010
+#define COMREG1_DMAEN 0x0020
+#define COMREG1_CONVINTEN 0x0080
+#define COMREG2_SCN2 0x0010
+#define COMREG2_INTEN 0x0080
+#define COMREG2_DOUTEN0 0x0100
+#define COMREG2_DOUTEN1 0x0200
+/* Status Register bits */
+#define STAT_AD_OVERRUN 0x0100
+#define STAT_AD_OVERFLOW 0x0200
+#define STAT_AD_DAQPROG 0x0800
+#define STAT_AD_CONVAVAIL 0x2000
+#define STAT_AD_DAQSTOPINT 0x4000
+/* AM9513A Counter/Timer defines */
+#define CLOCK_1_MHZ 0x8B25
+#define CLOCK_100_KHZ 0x8C25
+#define CLOCK_10_KHZ 0x8D25
+#define CLOCK_1_KHZ 0x8E25
+#define CLOCK_100_HZ 0x8F25
+
+struct atmio16_board_t {
+ const char *name;
+ int has_8255;
+};
+
+/* range structs */
+static const struct comedi_lrange range_atmio16d_ai_10_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_atmio16d_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_atmio16d_ai_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.02)
+ }
+};
+
+/* private data struct */
+struct atmio16d_private {
+ enum { adc_diff, adc_singleended } adc_mux;
+ enum { adc_bipolar10, adc_bipolar5, adc_unipolar10 } adc_range;
+ enum { adc_2comp, adc_straight } adc_coding;
+ enum { dac_bipolar, dac_unipolar } dac0_range, dac1_range;
+ enum { dac_internal, dac_external } dac0_reference, dac1_reference;
+ enum { dac_2comp, dac_straight } dac0_coding, dac1_coding;
+ const struct comedi_lrange *ao_range_type_list[2];
+ unsigned int com_reg_1_state; /* current state of command register 1 */
+ unsigned int com_reg_2_state; /* current state of command register 2 */
+};
+
+static void reset_counters(struct comedi_device *dev)
+{
+ /* Counter 2 */
+ outw(0xFFC2, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF02, dev->iobase + AM9513A_COM_REG);
+ outw(0x4, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0A, dev->iobase + AM9513A_COM_REG);
+ outw(0x3, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF42, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF42, dev->iobase + AM9513A_COM_REG);
+ /* Counter 3 */
+ outw(0xFFC4, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF03, dev->iobase + AM9513A_COM_REG);
+ outw(0x4, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0B, dev->iobase + AM9513A_COM_REG);
+ outw(0x3, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF44, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF44, dev->iobase + AM9513A_COM_REG);
+ /* Counter 4 */
+ outw(0xFFC8, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF04, dev->iobase + AM9513A_COM_REG);
+ outw(0x4, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0C, dev->iobase + AM9513A_COM_REG);
+ outw(0x3, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF48, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF48, dev->iobase + AM9513A_COM_REG);
+ /* Counter 5 */
+ outw(0xFFD0, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF05, dev->iobase + AM9513A_COM_REG);
+ outw(0x4, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0D, dev->iobase + AM9513A_COM_REG);
+ outw(0x3, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF50, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF50, dev->iobase + AM9513A_COM_REG);
+
+ outw(0, dev->iobase + AD_CLEAR_REG);
+}
+
+static void reset_atmio16d(struct comedi_device *dev)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ int i;
+
+ /* now we need to initialize the board */
+ outw(0, dev->iobase + COM_REG_1);
+ outw(0, dev->iobase + COM_REG_2);
+ outw(0, dev->iobase + MUX_GAIN_REG);
+ /* init AM9513A timer */
+ outw(0xFFFF, dev->iobase + AM9513A_COM_REG);
+ outw(0xFFEF, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF17, dev->iobase + AM9513A_COM_REG);
+ outw(0xF000, dev->iobase + AM9513A_DATA_REG);
+ for (i = 1; i <= 5; ++i) {
+ outw(0xFF00 + i, dev->iobase + AM9513A_COM_REG);
+ outw(0x0004, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF08 + i, dev->iobase + AM9513A_COM_REG);
+ outw(0x3, dev->iobase + AM9513A_DATA_REG);
+ }
+ outw(0xFF5F, dev->iobase + AM9513A_COM_REG);
+ /* timer init done */
+ outw(0, dev->iobase + AD_CLEAR_REG);
+ outw(0, dev->iobase + INT2CLR_REG);
+ /* select straight binary mode for Analog Input */
+ devpriv->com_reg_1_state |= 1;
+ outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+ devpriv->adc_coding = adc_straight;
+ /* zero the analog outputs */
+ outw(2048, dev->iobase + DAC0_REG);
+ outw(2048, dev->iobase + DAC1_REG);
+}
+
+static irqreturn_t atmio16d_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned short val;
+
+ val = inw(dev->iobase + AD_FIFO_REG);
+ comedi_buf_write_samples(s, &val, 1);
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int atmio16d_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) {
+ /* internal trigger */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ } else {
+#if 0
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+#endif
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
+#if 0
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER);
+#endif
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ return 0;
+}
+
+static int atmio16d_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int timer, base_clock;
+ unsigned int sample_count, tmp, chan, gain;
+ int i;
+
+ /* This is slowly becoming a working command interface. *
+ * It is still uber-experimental */
+
+ reset_counters(dev);
+
+ /* check if scanning multiple channels */
+ if (cmd->chanlist_len < 2) {
+ devpriv->com_reg_1_state &= ~COMREG1_SCANEN;
+ outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+ } else {
+ devpriv->com_reg_1_state |= COMREG1_SCANEN;
+ devpriv->com_reg_2_state |= COMREG2_SCN2;
+ outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+ outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2);
+ }
+
+ /* Setup the Mux-Gain Counter */
+ for (i = 0; i < cmd->chanlist_len; ++i) {
+ chan = CR_CHAN(cmd->chanlist[i]);
+ gain = CR_RANGE(cmd->chanlist[i]);
+ outw(i, dev->iobase + MUX_CNTR_REG);
+ tmp = chan | (gain << 6);
+ if (i == cmd->scan_end_arg - 1)
+ tmp |= 0x0010; /* set LASTONE bit */
+ outw(tmp, dev->iobase + MUX_GAIN_REG);
+ }
+
+ /* Now program the sample interval timer */
+ /* Figure out which clock to use then get an
+ * appropriate timer value */
+ if (cmd->convert_arg < 65536000) {
+ base_clock = CLOCK_1_MHZ;
+ timer = cmd->convert_arg / 1000;
+ } else if (cmd->convert_arg < 655360000) {
+ base_clock = CLOCK_100_KHZ;
+ timer = cmd->convert_arg / 10000;
+ } else /* cmd->convert_arg < 6553600000 */ {
+ base_clock = CLOCK_10_KHZ;
+ timer = cmd->convert_arg / 100000;
+ }
+ outw(0xFF03, dev->iobase + AM9513A_COM_REG);
+ outw(base_clock, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0B, dev->iobase + AM9513A_COM_REG);
+ outw(0x2, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF44, dev->iobase + AM9513A_COM_REG);
+ outw(0xFFF3, dev->iobase + AM9513A_COM_REG);
+ outw(timer, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF24, dev->iobase + AM9513A_COM_REG);
+
+ /* Now figure out how many samples to get */
+ /* and program the sample counter */
+ sample_count = cmd->stop_arg * cmd->scan_end_arg;
+ outw(0xFF04, dev->iobase + AM9513A_COM_REG);
+ outw(0x1025, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0C, dev->iobase + AM9513A_COM_REG);
+ if (sample_count < 65536) {
+ /* use only Counter 4 */
+ outw(sample_count, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF48, dev->iobase + AM9513A_COM_REG);
+ outw(0xFFF4, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF28, dev->iobase + AM9513A_COM_REG);
+ devpriv->com_reg_1_state &= ~COMREG1_1632CNT;
+ outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+ } else {
+ /* Counter 4 and 5 are needed */
+
+ tmp = sample_count & 0xFFFF;
+ if (tmp)
+ outw(tmp - 1, dev->iobase + AM9513A_DATA_REG);
+ else
+ outw(0xFFFF, dev->iobase + AM9513A_DATA_REG);
+
+ outw(0xFF48, dev->iobase + AM9513A_COM_REG);
+ outw(0, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF28, dev->iobase + AM9513A_COM_REG);
+ outw(0xFF05, dev->iobase + AM9513A_COM_REG);
+ outw(0x25, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0D, dev->iobase + AM9513A_COM_REG);
+ tmp = sample_count & 0xFFFF;
+ if ((tmp == 0) || (tmp == 1)) {
+ outw((sample_count >> 16) & 0xFFFF,
+ dev->iobase + AM9513A_DATA_REG);
+ } else {
+ outw(((sample_count >> 16) & 0xFFFF) + 1,
+ dev->iobase + AM9513A_DATA_REG);
+ }
+ outw(0xFF70, dev->iobase + AM9513A_COM_REG);
+ devpriv->com_reg_1_state |= COMREG1_1632CNT;
+ outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+ }
+
+ /* Program the scan interval timer ONLY IF SCANNING IS ENABLED */
+ /* Figure out which clock to use then get an
+ * appropriate timer value */
+ if (cmd->chanlist_len > 1) {
+ if (cmd->scan_begin_arg < 65536000) {
+ base_clock = CLOCK_1_MHZ;
+ timer = cmd->scan_begin_arg / 1000;
+ } else if (cmd->scan_begin_arg < 655360000) {
+ base_clock = CLOCK_100_KHZ;
+ timer = cmd->scan_begin_arg / 10000;
+ } else /* cmd->scan_begin_arg < 6553600000 */ {
+ base_clock = CLOCK_10_KHZ;
+ timer = cmd->scan_begin_arg / 100000;
+ }
+ outw(0xFF02, dev->iobase + AM9513A_COM_REG);
+ outw(base_clock, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF0A, dev->iobase + AM9513A_COM_REG);
+ outw(0x2, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF42, dev->iobase + AM9513A_COM_REG);
+ outw(0xFFF2, dev->iobase + AM9513A_COM_REG);
+ outw(timer, dev->iobase + AM9513A_DATA_REG);
+ outw(0xFF22, dev->iobase + AM9513A_COM_REG);
+ }
+
+ /* Clear the A/D FIFO and reset the MUX counter */
+ outw(0, dev->iobase + AD_CLEAR_REG);
+ outw(0, dev->iobase + MUX_CNTR_REG);
+ outw(0, dev->iobase + INT2CLR_REG);
+ /* enable this acquisition operation */
+ devpriv->com_reg_1_state |= COMREG1_DAQEN;
+ outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+ /* enable interrupts for conversion completion */
+ devpriv->com_reg_1_state |= COMREG1_CONVINTEN;
+ devpriv->com_reg_2_state |= COMREG2_INTEN;
+ outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+ outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2);
+ /* apply a trigger. this starts the counters! */
+ outw(0, dev->iobase + START_DAQ_REG);
+
+ return 0;
+}
+
+/* This will cancel a running acquisition operation */
+static int atmio16d_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ reset_atmio16d(dev);
+
+ return 0;
+}
+
+static int atmio16d_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + STAT_REG);
+ if (status & STAT_AD_CONVAVAIL)
+ return 0;
+ if (status & STAT_AD_OVERFLOW) {
+ outw(0, dev->iobase + AD_CLEAR_REG);
+ return -EOVERFLOW;
+ }
+ return -EBUSY;
+}
+
+static int atmio16d_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ int i;
+ int chan;
+ int gain;
+ int ret;
+
+ chan = CR_CHAN(insn->chanspec);
+ gain = CR_RANGE(insn->chanspec);
+
+ /* reset the Analog input circuitry */
+ /* outw( 0, dev->iobase+AD_CLEAR_REG ); */
+ /* reset the Analog Input MUX Counter to 0 */
+ /* outw( 0, dev->iobase+MUX_CNTR_REG ); */
+
+ /* set the Input MUX gain */
+ outw(chan | (gain << 6), dev->iobase + MUX_GAIN_REG);
+
+ for (i = 0; i < insn->n; i++) {
+ /* start the conversion */
+ outw(0, dev->iobase + START_CONVERT_REG);
+
+ /* wait for it to finish */
+ ret = comedi_timeout(dev, s, insn, atmio16d_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read the data now */
+ data[i] = inw(dev->iobase + AD_FIFO_REG);
+ /* change to two's complement if need be */
+ if (devpriv->adc_coding == adc_2comp)
+ data[i] ^= 0x800;
+ }
+
+ return i;
+}
+
+static int atmio16d_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int reg = (chan) ? DAC1_REG : DAC0_REG;
+ bool munge = false;
+ int i;
+
+ if (chan == 0 && devpriv->dac0_coding == dac_2comp)
+ munge = true;
+ if (chan == 1 && devpriv->dac1_coding == dac_2comp)
+ munge = true;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ if (munge)
+ val ^= 0x800;
+
+ outw(val, dev->iobase + reg);
+ }
+
+ return insn->n;
+}
+
+static int atmio16d_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + MIO_16_DIG_OUT_REG);
+
+ data[1] = inw(dev->iobase + MIO_16_DIG_IN_REG);
+
+ return insn->n;
+}
+
+static int atmio16d_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ devpriv->com_reg_2_state &= ~(COMREG2_DOUTEN0 | COMREG2_DOUTEN1);
+ if (s->io_bits & 0x0f)
+ devpriv->com_reg_2_state |= COMREG2_DOUTEN0;
+ if (s->io_bits & 0xf0)
+ devpriv->com_reg_2_state |= COMREG2_DOUTEN1;
+ outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2);
+
+ return insn->n;
+}
+
+/*
+ options[0] - I/O port
+ options[1] - MIO irq
+ 0 == no irq
+ N == irq N {3,4,5,6,7,9,10,11,12,14,15}
+ options[2] - DIO irq
+ 0 == no irq
+ N == irq N {3,4,5,6,7,9}
+ options[3] - DMA1 channel
+ 0 == no DMA
+ N == DMA N {5,6,7}
+ options[4] - DMA2 channel
+ 0 == no DMA
+ N == DMA N {5,6,7}
+
+ options[5] - a/d mux
+ 0=differential, 1=single
+ options[6] - a/d range
+ 0=bipolar10, 1=bipolar5, 2=unipolar10
+
+ options[7] - dac0 range
+ 0=bipolar, 1=unipolar
+ options[8] - dac0 reference
+ 0=internal, 1=external
+ options[9] - dac0 coding
+ 0=2's comp, 1=straight binary
+
+ options[10] - dac1 range
+ options[11] - dac1 reference
+ options[12] - dac1 coding
+ */
+
+static int atmio16d_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct atmio16_board_t *board = dev->board_ptr;
+ struct atmio16d_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x20);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* reset the atmio16d hardware */
+ reset_atmio16d(dev);
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], atmio16d_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ /* set device options */
+ devpriv->adc_mux = it->options[5];
+ devpriv->adc_range = it->options[6];
+
+ devpriv->dac0_range = it->options[7];
+ devpriv->dac0_reference = it->options[8];
+ devpriv->dac0_coding = it->options[9];
+ devpriv->dac1_range = it->options[10];
+ devpriv->dac1_reference = it->options[11];
+ devpriv->dac1_coding = it->options[12];
+
+ /* setup sub-devices */
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = (devpriv->adc_mux ? 16 : 8);
+ s->insn_read = atmio16d_ai_insn_read;
+ s->maxdata = 0xfff; /* 4095 decimal */
+ switch (devpriv->adc_range) {
+ case adc_bipolar10:
+ s->range_table = &range_atmio16d_ai_10_bipolar;
+ break;
+ case adc_bipolar5:
+ s->range_table = &range_atmio16d_ai_5_bipolar;
+ break;
+ case adc_unipolar10:
+ s->range_table = &range_atmio16d_ai_unipolar;
+ break;
+ }
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 16;
+ s->do_cmdtest = atmio16d_ai_cmdtest;
+ s->do_cmd = atmio16d_ai_cmd;
+ s->cancel = atmio16d_ai_cancel;
+ }
+
+ /* ao subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0xfff; /* 4095 decimal */
+ s->range_table_list = devpriv->ao_range_type_list;
+ switch (devpriv->dac0_range) {
+ case dac_bipolar:
+ devpriv->ao_range_type_list[0] = &range_bipolar10;
+ break;
+ case dac_unipolar:
+ devpriv->ao_range_type_list[0] = &range_unipolar10;
+ break;
+ }
+ switch (devpriv->dac1_range) {
+ case dac_bipolar:
+ devpriv->ao_range_type_list[1] = &range_bipolar10;
+ break;
+ case dac_unipolar:
+ devpriv->ao_range_type_list[1] = &range_unipolar10;
+ break;
+ }
+ s->insn_write = atmio16d_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital I/O */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 8;
+ s->insn_bits = atmio16d_dio_insn_bits;
+ s->insn_config = atmio16d_dio_insn_config;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+
+ /* 8255 subdevice */
+ s = &dev->subdevices[3];
+ if (board->has_8255) {
+ ret = subdev_8255_init(dev, s, NULL, 0x00);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+/* don't yet know how to deal with counter/timers */
+#if 0
+ s = &dev->subdevices[4];
+ /* do */
+ s->type = COMEDI_SUBD_TIMER;
+ s->n_chan = 0;
+ s->maxdata = 0
+#endif
+
+ return 0;
+}
+
+static void atmio16d_detach(struct comedi_device *dev)
+{
+ reset_atmio16d(dev);
+ comedi_legacy_detach(dev);
+}
+
+static const struct atmio16_board_t atmio16_boards[] = {
+ {
+ .name = "atmio16",
+ .has_8255 = 0,
+ }, {
+ .name = "atmio16d",
+ .has_8255 = 1,
+ },
+};
+
+static struct comedi_driver atmio16d_driver = {
+ .driver_name = "atmio16",
+ .module = THIS_MODULE,
+ .attach = atmio16d_attach,
+ .detach = atmio16d_detach,
+ .board_name = &atmio16_boards[0].name,
+ .num_names = ARRAY_SIZE(atmio16_boards),
+ .offset = sizeof(struct atmio16_board_t),
+};
+module_comedi_driver(atmio16d_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_daq_700.c b/drivers/staging/comedi/drivers/ni_daq_700.c
new file mode 100644
index 000000000..8f6396edd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_daq_700.c
@@ -0,0 +1,289 @@
+/*
+ * comedi/drivers/ni_daq_700.c
+ * Driver for DAQCard-700 DIO/AI
+ * copied from 8255
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_daq_700
+ * Description: National Instruments PCMCIA DAQCard-700
+ * Author: Fred Brooks <nsaspook@nsaspook.com>,
+ * based on ni_daq_dio24 by Daniel Vecino Castel <dvecino@able.es>
+ * Devices: [National Instruments] PCMCIA DAQ-Card-700 (ni_daq_700)
+ * Status: works
+ * Updated: Wed, 21 May 2014 12:07:20 +0000
+ *
+ * The daqcard-700 appears in Comedi as a digital I/O subdevice (0) with
+ * 16 channels and a analog input subdevice (1) with 16 single-ended channels
+ * or 8 differential channels, and three input ranges.
+ *
+ * Digital: The channel 0 corresponds to the daqcard-700's output
+ * port, bit 0; channel 8 corresponds to the input port, bit 0.
+ *
+ * Digital direction configuration: channels 0-7 output, 8-15 input.
+ *
+ * Analog: The input range is 0 to 4095 with a default of -10 to +10 volts.
+ * Valid ranges:
+ * 0 for -10 to 10V bipolar
+ * 1 for -5 to 5V bipolar
+ * 2 for -2.5 to 2.5V bipolar
+ *
+ * IRQ is assigned but not used.
+ *
+ * Manuals: Register level: http://www.ni.com/pdf/manuals/340698.pdf
+ * User Manual: http://www.ni.com/pdf/manuals/320676d.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pcmcia.h"
+
+/* daqcard700 registers */
+#define DIO_W 0x04 /* WO 8bit */
+#define DIO_R 0x05 /* RO 8bit */
+#define CMD_R1 0x00 /* WO 8bit */
+#define CMD_R2 0x07 /* RW 8bit */
+#define CMD_R3 0x05 /* W0 8bit */
+#define STA_R1 0x00 /* RO 8bit */
+#define STA_R2 0x01 /* RO 8bit */
+#define ADFIFO_R 0x02 /* RO 16bit */
+#define ADCLEAR_R 0x01 /* WO 8bit */
+#define CDA_R0 0x08 /* RW 8bit */
+#define CDA_R1 0x09 /* RW 8bit */
+#define CDA_R2 0x0A /* RW 8bit */
+#define CMO_R 0x0B /* RO 8bit */
+#define TIC_R 0x06 /* WO 8bit */
+/* daqcard700 modes */
+#define CMD_R3_DIFF 0x04 /* diff mode */
+
+static const struct comedi_lrange range_daq700_ai = {
+ 3,
+ {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5)
+ }
+};
+
+static int daq700_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ outb(s->state & 0xff, dev->iobase + DIO_W);
+ }
+
+ val = s->state & 0xff;
+ val |= inb(dev->iobase + DIO_R) << 8;
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int daq700_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /* The DIO channels are not configurable, fix the io_bits */
+ s->io_bits = 0x00ff;
+
+ return insn->n;
+}
+
+static int daq700_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + STA_R2);
+ if ((status & 0x03))
+ return -EOVERFLOW;
+ status = inb(dev->iobase + STA_R1);
+ if ((status & 0x02))
+ return -ENODATA;
+ if ((status & 0x11) == 0x01)
+ return 0;
+ return -EBUSY;
+}
+
+static int daq700_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int n;
+ int d;
+ int ret;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int r3_bits = 0;
+
+ /* set channel input modes */
+ if (aref == AREF_DIFF)
+ r3_bits |= CMD_R3_DIFF;
+ /* write channel mode/range */
+ if (range >= 1)
+ range++; /* convert range to hardware value */
+ outb(r3_bits | (range & 0x03), dev->iobase + CMD_R3);
+
+ /* write channel to multiplexer */
+ /* set mask scan bit high to disable scanning */
+ outb(chan | 0x80, dev->iobase + CMD_R1);
+ /* mux needs 2us to really settle [Fred Brooks]. */
+ udelay(2);
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion with out0 L to H */
+ outb(0x00, dev->iobase + CMD_R2); /* enable ADC conversions */
+ outb(0x30, dev->iobase + CMO_R); /* mode 0 out0 L, from H */
+ outb(0x00, dev->iobase + ADCLEAR_R); /* clear the ADC FIFO */
+ /* read 16bit junk from FIFO to clear */
+ inw(dev->iobase + ADFIFO_R);
+ /* mode 1 out0 H, L to H, start conversion */
+ outb(0x32, dev->iobase + CMO_R);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, daq700_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ d = inw(dev->iobase + ADFIFO_R);
+ /* mangle the data as necessary */
+ /* Bipolar Offset Binary: 0 to 4095 for -10 to +10 */
+ d &= 0x0fff;
+ d ^= 0x0800;
+ data[n] = d;
+ }
+ return n;
+}
+
+/*
+ * Data acquisition is enabled.
+ * The counter 0 output is high.
+ * The I/O connector pin CLK1 drives counter 1 source.
+ * Multiple-channel scanning is disabled.
+ * All interrupts are disabled.
+ * The analog input range is set to +-10 V
+ * The analog input mode is single-ended.
+ * The analog input circuitry is initialized to channel 0.
+ * The A/D FIFO is cleared.
+ */
+static void daq700_ai_config(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned long iobase = dev->iobase;
+
+ outb(0x80, iobase + CMD_R1); /* disable scanning, ADC to chan 0 */
+ outb(0x00, iobase + CMD_R2); /* clear all bits */
+ outb(0x00, iobase + CMD_R3); /* set +-10 range */
+ outb(0x32, iobase + CMO_R); /* config counter mode1, out0 to H */
+ outb(0x00, iobase + TIC_R); /* clear counter interrupt */
+ outb(0x00, iobase + ADCLEAR_R); /* clear the ADC FIFO */
+ inw(iobase + ADFIFO_R); /* read 16bit junk from FIFO to clear */
+}
+
+static int daq700_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ link->config_flags |= CONF_AUTO_SET_IO;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* DAQCard-700 dio */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 16;
+ s->range_table = &range_digital;
+ s->maxdata = 1;
+ s->insn_bits = daq700_dio_insn_bits;
+ s->insn_config = daq700_dio_insn_config;
+ s->io_bits = 0x00ff;
+
+ /* DAQCard-700 ai */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = (1 << 12) - 1;
+ s->range_table = &range_daq700_ai;
+ s->insn_read = daq700_ai_rinsn;
+ daq700_ai_config(dev, s);
+
+ return 0;
+}
+
+static struct comedi_driver daq700_driver = {
+ .driver_name = "ni_daq_700",
+ .module = THIS_MODULE,
+ .auto_attach = daq700_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int daq700_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &daq700_driver);
+}
+
+static const struct pcmcia_device_id daq700_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4743),
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, daq700_cs_ids);
+
+static struct pcmcia_driver daq700_cs_driver = {
+ .name = "ni_daq_700",
+ .owner = THIS_MODULE,
+ .id_table = daq700_cs_ids,
+ .probe = daq700_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(daq700_driver, daq700_cs_driver);
+
+MODULE_AUTHOR("Fred Brooks <nsaspook@nsaspook.com>");
+MODULE_DESCRIPTION(
+ "Comedi driver for National Instruments PCMCIA DAQCard-700 DIO/AI");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_daq_dio24.c b/drivers/staging/comedi/drivers/ni_daq_dio24.c
new file mode 100644
index 000000000..a208cb348
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_daq_dio24.c
@@ -0,0 +1,95 @@
+/*
+ comedi/drivers/ni_daq_dio24.c
+ Driver for National Instruments PCMCIA DAQ-Card DIO-24
+ Copyright (C) 2002 Daniel Vecino Castel <dvecino@able.es>
+
+ PCMCIA crap at end of file is adapted from dummy_cs.c 1.31
+ 2001/08/24 12:13:13 from the pcmcia package.
+ The initial developer of the pcmcia dummy_cs.c code is David A. Hinds
+ <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
+ are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_daq_dio24
+Description: National Instruments PCMCIA DAQ-Card DIO-24
+Author: Daniel Vecino Castel <dvecino@able.es>
+Devices: [National Instruments] PCMCIA DAQ-Card DIO-24 (ni_daq_dio24)
+Status: ?
+Updated: Thu, 07 Nov 2002 21:53:06 -0800
+
+This is just a wrapper around the 8255.o driver to properly handle
+the PCMCIA interface.
+*/
+
+#include <linux/module.h>
+#include "../comedi_pcmcia.h"
+
+#include "8255.h"
+
+static int dio24_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ link->config_flags |= CONF_AUTO_SET_IO;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* 8255 dio */
+ s = &dev->subdevices[0];
+ ret = subdev_8255_init(dev, s, NULL, 0x00);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver driver_dio24 = {
+ .driver_name = "ni_daq_dio24",
+ .module = THIS_MODULE,
+ .auto_attach = dio24_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int dio24_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_dio24);
+}
+
+static const struct pcmcia_device_id dio24_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x475c), /* daqcard-dio24 */
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, dio24_cs_ids);
+
+static struct pcmcia_driver dio24_cs_driver = {
+ .name = "ni_daq_dio24",
+ .owner = THIS_MODULE,
+ .id_table = dio24_cs_ids,
+ .probe = dio24_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_dio24, dio24_cs_driver);
+
+MODULE_AUTHOR("Daniel Vecino Castel <dvecino@able.es>");
+MODULE_DESCRIPTION(
+ "Comedi driver for National Instruments PCMCIA DAQ-Card DIO-24");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc.c b/drivers/staging/comedi/drivers/ni_labpc.c
new file mode 100644
index 000000000..51e5e942b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc.c
@@ -0,0 +1,125 @@
+/*
+ * comedi/drivers/ni_labpc.c
+ * Driver for National Instruments Lab-PC series boards and compatibles
+ * Copyright (C) 2001-2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_labpc
+ * Description: National Instruments Lab-PC (& compatibles)
+ * Devices: [National Instruments] Lab-PC-1200 (lab-pc-1200),
+ * Lab-PC-1200AI (lab-pc-1200ai), Lab-PC+ (lab-pc+)
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ *
+ * Configuration options - ISA boards:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional, required for timed or externally triggered
+ * conversions)
+ * [2] - DMA channel (optional)
+ *
+ * Tested with lab-pc-1200. For the older Lab-PC+, not all input
+ * ranges and analog references will work, the available ranges/arefs
+ * will depend on how you have configured the jumpers on your board
+ * (see your owner's manual).
+ *
+ * Kernel-level ISA plug-and-play support for the lab-pc-1200 boards
+ * has not yet been added to the driver, mainly due to the fact that
+ * I don't know the device id numbers. If you have one of these boards,
+ * please file a bug report at http://comedi.org/ so I can get the
+ * necessary information from you.
+ *
+ * The 1200 series boards have onboard calibration dacs for correcting
+ * analog input/output offsets and gains. The proper settings for these
+ * caldacs are stored on the board's eeprom. To read the caldac values
+ * from the eeprom and store them into a file that can be then be used
+ * by comedilib, use the comedi_calibrate program.
+ *
+ * The Lab-pc+ has quirky chanlist requirements when scanning multiple
+ * channels. Multiple channel scan sequence must start at highest channel,
+ * then decrement down to channel 0. The rest of the cards can scan down
+ * like lab-pc+ or scan up from channel zero. Chanlists consisting of all
+ * one channel are also legal, and allow you to pace conversions in bursts.
+ *
+ * NI manuals:
+ * 341309a (labpc-1200 register manual)
+ * 320502b (lab-pc+)
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+#include "ni_labpc.h"
+#include "ni_labpc_isadma.h"
+
+static const struct labpc_boardinfo labpc_boards[] = {
+ {
+ .name = "lab-pc-1200",
+ .ai_speed = 10000,
+ .ai_scan_up = 1,
+ .has_ao = 1,
+ .is_labpc1200 = 1,
+ }, {
+ .name = "lab-pc-1200ai",
+ .ai_speed = 10000,
+ .ai_scan_up = 1,
+ .is_labpc1200 = 1,
+ }, {
+ .name = "lab-pc+",
+ .ai_speed = 12000,
+ .has_ao = 1,
+ },
+};
+
+static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ unsigned int irq = it->options[1];
+ unsigned int dma_chan = it->options[2];
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x20);
+ if (ret)
+ return ret;
+
+ ret = labpc_common_attach(dev, irq, 0);
+ if (ret)
+ return ret;
+
+ if (dev->irq)
+ labpc_init_dma_chan(dev, dma_chan);
+
+ return 0;
+}
+
+static void labpc_detach(struct comedi_device *dev)
+{
+ labpc_free_dma_chan(dev);
+ labpc_common_detach(dev);
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver labpc_driver = {
+ .driver_name = "ni_labpc",
+ .module = THIS_MODULE,
+ .attach = labpc_attach,
+ .detach = labpc_detach,
+ .num_names = ARRAY_SIZE(labpc_boards),
+ .board_name = &labpc_boards[0].name,
+ .offset = sizeof(struct labpc_boardinfo),
+};
+module_comedi_driver(labpc_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for NI Lab-PC ISA boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc.h b/drivers/staging/comedi/drivers/ni_labpc.h
new file mode 100644
index 000000000..83f878adb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc.h
@@ -0,0 +1,69 @@
+/*
+ ni_labpc.h
+
+ Header for ni_labpc.c and ni_labpc_cs.c
+
+ Copyright (C) 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _NI_LABPC_H
+#define _NI_LABPC_H
+
+#define EEPROM_SIZE 256 /* 256 byte eeprom */
+#define NUM_AO_CHAN 2 /* boards have two analog output channels */
+
+enum transfer_type { fifo_not_empty_transfer, fifo_half_full_transfer,
+ isa_dma_transfer
+};
+
+struct labpc_boardinfo {
+ const char *name;
+ int ai_speed; /* maximum input speed in ns */
+ unsigned ai_scan_up:1; /* can auto scan up in ai channels */
+ unsigned has_ao:1; /* has analog outputs */
+ unsigned is_labpc1200:1; /* has extra regs compared to pc+ */
+};
+
+struct labpc_private {
+ struct comedi_isadma *dma;
+ struct comedi_8254 *counter;
+
+ /* number of data points left to be taken */
+ unsigned long long count;
+ /* software copys of bits written to command registers */
+ unsigned int cmd1;
+ unsigned int cmd2;
+ unsigned int cmd3;
+ unsigned int cmd4;
+ unsigned int cmd5;
+ unsigned int cmd6;
+ /* store last read of board status registers */
+ unsigned int stat1;
+ unsigned int stat2;
+
+ /* we are using dma/fifo-half-full/etc. */
+ enum transfer_type current_transfer;
+ /*
+ * function pointers so we can use inb/outb or readb/writeb as
+ * appropriate
+ */
+ unsigned int (*read_byte)(struct comedi_device *, unsigned long reg);
+ void (*write_byte)(struct comedi_device *,
+ unsigned int byte, unsigned long reg);
+};
+
+int labpc_common_attach(struct comedi_device *dev,
+ unsigned int irq, unsigned long isr_flags);
+void labpc_common_detach(struct comedi_device *dev);
+
+#endif /* _NI_LABPC_H */
diff --git a/drivers/staging/comedi/drivers/ni_labpc_common.c b/drivers/staging/comedi/drivers/ni_labpc_common.c
new file mode 100644
index 000000000..863afb28e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_common.c
@@ -0,0 +1,1355 @@
+/*
+ * comedi/drivers/ni_labpc_common.c
+ *
+ * Common support code for "ni_labpc", "ni_labpc_pci" and "ni_labpc_cs".
+ *
+ * Copyright (C) 2001-2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include "../comedidev.h"
+
+#include "comedi_8254.h"
+#include "8255.h"
+#include "ni_labpc.h"
+#include "ni_labpc_regs.h"
+#include "ni_labpc_isadma.h"
+
+enum scan_mode {
+ MODE_SINGLE_CHAN,
+ MODE_SINGLE_CHAN_INTERVAL,
+ MODE_MULT_CHAN_UP,
+ MODE_MULT_CHAN_DOWN,
+};
+
+static const struct comedi_lrange range_labpc_plus_ai = {
+ 16, {
+ BIP_RANGE(5),
+ BIP_RANGE(4),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.25),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(10),
+ UNI_RANGE(8),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.2),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_labpc_1200_ai = {
+ 14, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.25),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.2),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_labpc_ao = {
+ 2, {
+ BIP_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+/* functions that do inb/outb and readb/writeb so we can use
+ * function pointers to decide which to use */
+static unsigned int labpc_inb(struct comedi_device *dev, unsigned long reg)
+{
+ return inb(dev->iobase + reg);
+}
+
+static void labpc_outb(struct comedi_device *dev,
+ unsigned int byte, unsigned long reg)
+{
+ outb(byte, dev->iobase + reg);
+}
+
+static unsigned int labpc_readb(struct comedi_device *dev, unsigned long reg)
+{
+ return readb(dev->mmio + reg);
+}
+
+static void labpc_writeb(struct comedi_device *dev,
+ unsigned int byte, unsigned long reg)
+{
+ writeb(byte, dev->mmio + reg);
+}
+
+static int labpc_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->cmd2 &= ~(CMD2_SWTRIG | CMD2_HWTRIG | CMD2_PRETRIG);
+ devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ devpriv->cmd3 = 0;
+ devpriv->write_byte(dev, devpriv->cmd3, CMD3_REG);
+
+ return 0;
+}
+
+static void labpc_ai_set_chan_and_gain(struct comedi_device *dev,
+ enum scan_mode mode,
+ unsigned int chan,
+ unsigned int range,
+ unsigned int aref)
+{
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ struct labpc_private *devpriv = dev->private;
+
+ if (board->is_labpc1200) {
+ /*
+ * The LabPC-1200 boards do not have a gain
+ * of '0x10'. Skip the range values that would
+ * result in this gain.
+ */
+ range += (range > 0) + (range > 7);
+ }
+
+ /* munge channel bits for differential/scan disabled mode */
+ if ((mode == MODE_SINGLE_CHAN || mode == MODE_SINGLE_CHAN_INTERVAL) &&
+ aref == AREF_DIFF)
+ chan *= 2;
+ devpriv->cmd1 = CMD1_MA(chan);
+ devpriv->cmd1 |= CMD1_GAIN(range);
+
+ devpriv->write_byte(dev, devpriv->cmd1, CMD1_REG);
+}
+
+static void labpc_setup_cmd6_reg(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ enum scan_mode mode,
+ enum transfer_type xfer,
+ unsigned int range,
+ unsigned int aref,
+ bool ena_intr)
+{
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ struct labpc_private *devpriv = dev->private;
+
+ if (!board->is_labpc1200)
+ return;
+
+ /* reference inputs to ground or common? */
+ if (aref != AREF_GROUND)
+ devpriv->cmd6 |= CMD6_NRSE;
+ else
+ devpriv->cmd6 &= ~CMD6_NRSE;
+
+ /* bipolar or unipolar range? */
+ if (comedi_range_is_unipolar(s, range))
+ devpriv->cmd6 |= CMD6_ADCUNI;
+ else
+ devpriv->cmd6 &= ~CMD6_ADCUNI;
+
+ /* interrupt on fifo half full? */
+ if (xfer == fifo_half_full_transfer)
+ devpriv->cmd6 |= CMD6_HFINTEN;
+ else
+ devpriv->cmd6 &= ~CMD6_HFINTEN;
+
+ /* enable interrupt on counter a1 terminal count? */
+ if (ena_intr)
+ devpriv->cmd6 |= CMD6_DQINTEN;
+ else
+ devpriv->cmd6 &= ~CMD6_DQINTEN;
+
+ /* are we scanning up or down through channels? */
+ if (mode == MODE_MULT_CHAN_UP)
+ devpriv->cmd6 |= CMD6_SCANUP;
+ else
+ devpriv->cmd6 &= ~CMD6_SCANUP;
+
+ devpriv->write_byte(dev, devpriv->cmd6, CMD6_REG);
+}
+
+static unsigned int labpc_read_adc_fifo(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int lsb = devpriv->read_byte(dev, ADC_FIFO_REG);
+ unsigned int msb = devpriv->read_byte(dev, ADC_FIFO_REG);
+
+ return (msb << 8) | lsb;
+}
+
+static void labpc_clear_adc_fifo(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ devpriv->write_byte(dev, 0x1, ADC_FIFO_CLEAR_REG);
+ labpc_read_adc_fifo(dev);
+}
+
+static int labpc_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG);
+ if (devpriv->stat1 & STAT1_DAVAIL)
+ return 0;
+ return -EBUSY;
+}
+
+static int labpc_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ int ret;
+ int i;
+
+ /* disable timed conversions, interrupt generation and dma */
+ labpc_cancel(dev, s);
+
+ labpc_ai_set_chan_and_gain(dev, MODE_SINGLE_CHAN, chan, range, aref);
+
+ labpc_setup_cmd6_reg(dev, s, MODE_SINGLE_CHAN, fifo_not_empty_transfer,
+ range, aref, false);
+
+ /* setup cmd4 register */
+ devpriv->cmd4 = 0;
+ devpriv->cmd4 |= CMD4_ECLKRCV;
+ /* single-ended/differential */
+ if (aref == AREF_DIFF)
+ devpriv->cmd4 |= CMD4_SEDIFF;
+ devpriv->write_byte(dev, devpriv->cmd4, CMD4_REG);
+
+ /* initialize pacer counter to prevent any problems */
+ comedi_8254_set_mode(devpriv->counter, 0, I8254_MODE2 | I8254_BINARY);
+
+ labpc_clear_adc_fifo(dev);
+
+ for (i = 0; i < insn->n; i++) {
+ /* trigger conversion */
+ devpriv->write_byte(dev, 0x1, ADC_START_CONVERT_REG);
+
+ ret = comedi_timeout(dev, s, insn, labpc_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = labpc_read_adc_fifo(dev);
+ }
+
+ return insn->n;
+}
+
+static bool labpc_use_continuous_mode(const struct comedi_cmd *cmd,
+ enum scan_mode mode)
+{
+ if (mode == MODE_SINGLE_CHAN || cmd->scan_begin_src == TRIG_FOLLOW)
+ return true;
+
+ return false;
+}
+
+static unsigned int labpc_ai_convert_period(const struct comedi_cmd *cmd,
+ enum scan_mode mode)
+{
+ if (cmd->convert_src != TRIG_TIMER)
+ return 0;
+
+ if (mode == MODE_SINGLE_CHAN && cmd->scan_begin_src == TRIG_TIMER)
+ return cmd->scan_begin_arg;
+
+ return cmd->convert_arg;
+}
+
+static void labpc_set_ai_convert_period(struct comedi_cmd *cmd,
+ enum scan_mode mode, unsigned int ns)
+{
+ if (cmd->convert_src != TRIG_TIMER)
+ return;
+
+ if (mode == MODE_SINGLE_CHAN &&
+ cmd->scan_begin_src == TRIG_TIMER) {
+ cmd->scan_begin_arg = ns;
+ if (cmd->convert_arg > cmd->scan_begin_arg)
+ cmd->convert_arg = cmd->scan_begin_arg;
+ } else {
+ cmd->convert_arg = ns;
+ }
+}
+
+static unsigned int labpc_ai_scan_period(const struct comedi_cmd *cmd,
+ enum scan_mode mode)
+{
+ if (cmd->scan_begin_src != TRIG_TIMER)
+ return 0;
+
+ if (mode == MODE_SINGLE_CHAN && cmd->convert_src == TRIG_TIMER)
+ return 0;
+
+ return cmd->scan_begin_arg;
+}
+
+static void labpc_set_ai_scan_period(struct comedi_cmd *cmd,
+ enum scan_mode mode, unsigned int ns)
+{
+ if (cmd->scan_begin_src != TRIG_TIMER)
+ return;
+
+ if (mode == MODE_SINGLE_CHAN && cmd->convert_src == TRIG_TIMER)
+ return;
+
+ cmd->scan_begin_arg = ns;
+}
+
+/* figures out what counter values to use based on command */
+static void labpc_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd,
+ enum scan_mode mode)
+{
+ struct comedi_8254 *pacer = dev->pacer;
+ unsigned int convert_period = labpc_ai_convert_period(cmd, mode);
+ unsigned int scan_period = labpc_ai_scan_period(cmd, mode);
+ unsigned int base_period;
+
+ /*
+ * If both convert and scan triggers are TRIG_TIMER, then they
+ * both rely on counter b0. If only one TRIG_TIMER is used, we
+ * can use the generic cascaded timing functions.
+ */
+ if (convert_period && scan_period) {
+ /*
+ * pick the lowest divisor value we can (for maximum input
+ * clock speed on convert and scan counters)
+ */
+ pacer->next_div1 = (scan_period - 1) /
+ (pacer->osc_base * I8254_MAX_COUNT) + 1;
+
+ comedi_check_trigger_arg_min(&pacer->next_div1, 2);
+ comedi_check_trigger_arg_max(&pacer->next_div1,
+ I8254_MAX_COUNT);
+
+ base_period = pacer->osc_base * pacer->next_div1;
+
+ /* set a0 for conversion frequency and b1 for scan frequency */
+ switch (cmd->flags & CMDF_ROUND_MASK) {
+ default:
+ case CMDF_ROUND_NEAREST:
+ pacer->next_div = DIV_ROUND_CLOSEST(convert_period,
+ base_period);
+ pacer->next_div2 = DIV_ROUND_CLOSEST(scan_period,
+ base_period);
+ break;
+ case CMDF_ROUND_UP:
+ pacer->next_div = DIV_ROUND_UP(convert_period,
+ base_period);
+ pacer->next_div2 = DIV_ROUND_UP(scan_period,
+ base_period);
+ break;
+ case CMDF_ROUND_DOWN:
+ pacer->next_div = convert_period / base_period;
+ pacer->next_div2 = scan_period / base_period;
+ break;
+ }
+ /* make sure a0 and b1 values are acceptable */
+ comedi_check_trigger_arg_min(&pacer->next_div, 2);
+ comedi_check_trigger_arg_max(&pacer->next_div, I8254_MAX_COUNT);
+ comedi_check_trigger_arg_min(&pacer->next_div2, 2);
+ comedi_check_trigger_arg_max(&pacer->next_div2,
+ I8254_MAX_COUNT);
+
+ /* write corrected timings to command */
+ labpc_set_ai_convert_period(cmd, mode,
+ base_period * pacer->next_div);
+ labpc_set_ai_scan_period(cmd, mode,
+ base_period * pacer->next_div2);
+ } else if (scan_period) {
+ /*
+ * calculate cascaded counter values
+ * that give desired scan timing
+ * (pacer->next_div2 / pacer->next_div1)
+ */
+ comedi_8254_cascade_ns_to_timer(pacer, &scan_period,
+ cmd->flags);
+ labpc_set_ai_scan_period(cmd, mode, scan_period);
+ } else if (convert_period) {
+ /*
+ * calculate cascaded counter values
+ * that give desired conversion timing
+ * (pacer->next_div / pacer->next_div1)
+ */
+ comedi_8254_cascade_ns_to_timer(pacer, &convert_period,
+ cmd->flags);
+ /* transfer div2 value so correct timer gets updated */
+ pacer->next_div = pacer->next_div2;
+ labpc_set_ai_convert_period(cmd, mode, convert_period);
+ }
+}
+
+static enum scan_mode labpc_ai_scan_mode(const struct comedi_cmd *cmd)
+{
+ unsigned int chan0;
+ unsigned int chan1;
+
+ if (cmd->chanlist_len == 1)
+ return MODE_SINGLE_CHAN;
+
+ /* chanlist may be NULL during cmdtest */
+ if (!cmd->chanlist)
+ return MODE_MULT_CHAN_UP;
+
+ chan0 = CR_CHAN(cmd->chanlist[0]);
+ chan1 = CR_CHAN(cmd->chanlist[1]);
+
+ if (chan0 < chan1)
+ return MODE_MULT_CHAN_UP;
+
+ if (chan0 > chan1)
+ return MODE_MULT_CHAN_DOWN;
+
+ return MODE_SINGLE_CHAN_INTERVAL;
+}
+
+static int labpc_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ enum scan_mode mode = labpc_ai_scan_mode(cmd);
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ switch (mode) {
+ case MODE_SINGLE_CHAN:
+ break;
+ case MODE_SINGLE_CHAN_INTERVAL:
+ if (chan != chan0) {
+ dev_dbg(dev->class_dev,
+ "channel scanning order specified in chanlist is not supported by hardware\n");
+ return -EINVAL;
+ }
+ break;
+ case MODE_MULT_CHAN_UP:
+ if (chan != i) {
+ dev_dbg(dev->class_dev,
+ "channel scanning order specified in chanlist is not supported by hardware\n");
+ return -EINVAL;
+ }
+ break;
+ case MODE_MULT_CHAN_DOWN:
+ if (chan != (cmd->chanlist_len - i - 1)) {
+ dev_dbg(dev->class_dev,
+ "channel scanning order specified in chanlist is not supported by hardware\n");
+ return -EINVAL;
+ }
+ break;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same range\n");
+ return -EINVAL;
+ }
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same reference\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int labpc_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ int err = 0;
+ int tmp, tmp2;
+ unsigned int stop_mask;
+ enum scan_mode mode;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+
+ stop_mask = TRIG_COUNT | TRIG_NONE;
+ if (board->is_labpc1200)
+ stop_mask |= TRIG_EXT;
+ err |= comedi_check_trigger_src(&cmd->stop_src, stop_mask);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /* can't have external stop and start triggers at once */
+ if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT)
+ err++;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /* start_arg value is ignored */
+ break;
+ }
+
+ if (!cmd->chanlist_len)
+ err |= -EINVAL;
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_speed);
+ }
+
+ /* make sure scan timing is not too fast */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ cmd->convert_arg *
+ cmd->chanlist_len);
+ }
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ board->ai_speed *
+ cmd->chanlist_len);
+ }
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ /*
+ * TRIG_EXT doesn't care since it doesn't
+ * trigger off a numbered channel
+ */
+ default:
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ tmp = cmd->convert_arg;
+ tmp2 = cmd->scan_begin_arg;
+ mode = labpc_ai_scan_mode(cmd);
+ labpc_adc_timing(dev, cmd, mode);
+ if (tmp != cmd->convert_arg || tmp2 != cmd->scan_begin_arg)
+ err++;
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= labpc_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int labpc_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ enum scan_mode mode = labpc_ai_scan_mode(cmd);
+ unsigned int chanspec = (mode == MODE_MULT_CHAN_UP) ?
+ cmd->chanlist[cmd->chanlist_len - 1] :
+ cmd->chanlist[0];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ enum transfer_type xfer;
+ unsigned long flags;
+
+ /* make sure board is disabled before setting up acquisition */
+ labpc_cancel(dev, s);
+
+ /* initialize software conversion count */
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->count = cmd->stop_arg * cmd->chanlist_len;
+
+ /* setup hardware conversion counter */
+ if (cmd->stop_src == TRIG_EXT) {
+ /*
+ * load counter a1 with count of 3
+ * (pc+ manual says this is minimum allowed) using mode 0
+ */
+ comedi_8254_load(devpriv->counter, 1,
+ 3, I8254_MODE0 | I8254_BINARY);
+ } else {
+ /* just put counter a1 in mode 0 to set its output low */
+ comedi_8254_set_mode(devpriv->counter, 1,
+ I8254_MODE0 | I8254_BINARY);
+ }
+
+ /* figure out what method we will use to transfer data */
+ if (devpriv->dma &&
+ /* dma unsafe at RT priority,
+ * and too much setup time for CMDF_WAKE_EOS */
+ (cmd->flags & (CMDF_WAKE_EOS | CMDF_PRIORITY)) == 0)
+ xfer = isa_dma_transfer;
+ else if (/* pc-plus has no fifo-half full interrupt */
+ board->is_labpc1200 &&
+ /* wake-end-of-scan should interrupt on fifo not empty */
+ (cmd->flags & CMDF_WAKE_EOS) == 0 &&
+ /* make sure we are taking more than just a few points */
+ (cmd->stop_src != TRIG_COUNT || devpriv->count > 256))
+ xfer = fifo_half_full_transfer;
+ else
+ xfer = fifo_not_empty_transfer;
+ devpriv->current_transfer = xfer;
+
+ labpc_ai_set_chan_and_gain(dev, mode, chan, range, aref);
+
+ labpc_setup_cmd6_reg(dev, s, mode, xfer, range, aref,
+ (cmd->stop_src == TRIG_EXT));
+
+ /* manual says to set scan enable bit on second pass */
+ if (mode == MODE_MULT_CHAN_UP || mode == MODE_MULT_CHAN_DOWN) {
+ devpriv->cmd1 |= CMD1_SCANEN;
+ /* need a brief delay before enabling scan, or scan
+ * list will get screwed when you switch
+ * between scan up to scan down mode - dunno why */
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd1, CMD1_REG);
+ }
+
+ devpriv->write_byte(dev, cmd->chanlist_len, INTERVAL_COUNT_REG);
+ /* load count */
+ devpriv->write_byte(dev, 0x1, INTERVAL_STROBE_REG);
+
+ if (cmd->convert_src == TRIG_TIMER ||
+ cmd->scan_begin_src == TRIG_TIMER) {
+ struct comedi_8254 *pacer = dev->pacer;
+ struct comedi_8254 *counter = devpriv->counter;
+
+ comedi_8254_update_divisors(pacer);
+
+ /* set up pacing */
+ comedi_8254_load(pacer, 0, pacer->divisor1,
+ I8254_MODE3 | I8254_BINARY);
+
+ /* set up conversion pacing */
+ comedi_8254_set_mode(counter, 0, I8254_MODE2 | I8254_BINARY);
+ if (labpc_ai_convert_period(cmd, mode))
+ comedi_8254_write(counter, 0, pacer->divisor);
+
+ /* set up scan pacing */
+ if (labpc_ai_scan_period(cmd, mode))
+ comedi_8254_load(pacer, 1, pacer->divisor2,
+ I8254_MODE2 | I8254_BINARY);
+ }
+
+ labpc_clear_adc_fifo(dev);
+
+ if (xfer == isa_dma_transfer)
+ labpc_setup_dma(dev, s);
+
+ /* enable error interrupts */
+ devpriv->cmd3 |= CMD3_ERRINTEN;
+ /* enable fifo not empty interrupt? */
+ if (xfer == fifo_not_empty_transfer)
+ devpriv->cmd3 |= CMD3_FIFOINTEN;
+ devpriv->write_byte(dev, devpriv->cmd3, CMD3_REG);
+
+ /* setup any external triggering/pacing (cmd4 register) */
+ devpriv->cmd4 = 0;
+ if (cmd->convert_src != TRIG_EXT)
+ devpriv->cmd4 |= CMD4_ECLKRCV;
+ /* XXX should discard first scan when using interval scanning
+ * since manual says it is not synced with scan clock */
+ if (!labpc_use_continuous_mode(cmd, mode)) {
+ devpriv->cmd4 |= CMD4_INTSCAN;
+ if (cmd->scan_begin_src == TRIG_EXT)
+ devpriv->cmd4 |= CMD4_EOIRCV;
+ }
+ /* single-ended/differential */
+ if (aref == AREF_DIFF)
+ devpriv->cmd4 |= CMD4_SEDIFF;
+ devpriv->write_byte(dev, devpriv->cmd4, CMD4_REG);
+
+ /* startup acquisition */
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ /* use 2 cascaded counters for pacing */
+ devpriv->cmd2 |= CMD2_TBSEL;
+
+ devpriv->cmd2 &= ~(CMD2_SWTRIG | CMD2_HWTRIG | CMD2_PRETRIG);
+ if (cmd->start_src == TRIG_EXT)
+ devpriv->cmd2 |= CMD2_HWTRIG;
+ else
+ devpriv->cmd2 |= CMD2_SWTRIG;
+ if (cmd->stop_src == TRIG_EXT)
+ devpriv->cmd2 |= (CMD2_HWTRIG | CMD2_PRETRIG);
+
+ devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return 0;
+}
+
+/* read all available samples from ai fifo */
+static int labpc_drain_fifo(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_async *async = dev->read_subdev->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned short data;
+ const int timeout = 10000;
+ unsigned int i;
+
+ devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG);
+
+ for (i = 0; (devpriv->stat1 & STAT1_DAVAIL) && i < timeout;
+ i++) {
+ /* quit if we have all the data we want */
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->count == 0)
+ break;
+ devpriv->count--;
+ }
+ data = labpc_read_adc_fifo(dev);
+ comedi_buf_write_samples(dev->read_subdev, &data, 1);
+ devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG);
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev, "ai timeout, fifo never empties\n");
+ async->events |= COMEDI_CB_ERROR;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* makes sure all data acquired by board is transferred to comedi (used
+ * when acquisition is terminated by stop_src == TRIG_EXT). */
+static void labpc_drain_dregs(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ if (devpriv->current_transfer == isa_dma_transfer)
+ labpc_drain_dma(dev);
+
+ labpc_drain_fifo(dev);
+}
+
+/* interrupt service routine */
+static irqreturn_t labpc_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+
+ if (!dev->attached) {
+ dev_err(dev->class_dev, "premature interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ async = s->async;
+ cmd = &async->cmd;
+
+ /* read board status */
+ devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG);
+ if (board->is_labpc1200)
+ devpriv->stat2 = devpriv->read_byte(dev, STAT2_REG);
+
+ if ((devpriv->stat1 & (STAT1_GATA0 | STAT1_CNTINT | STAT1_OVERFLOW |
+ STAT1_OVERRUN | STAT1_DAVAIL)) == 0 &&
+ (devpriv->stat2 & STAT2_OUTA1) == 0 &&
+ (devpriv->stat2 & STAT2_FIFONHF)) {
+ return IRQ_NONE;
+ }
+
+ if (devpriv->stat1 & STAT1_OVERRUN) {
+ /* clear error interrupt */
+ devpriv->write_byte(dev, 0x1, ADC_FIFO_CLEAR_REG);
+ async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+ dev_err(dev->class_dev, "overrun\n");
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->current_transfer == isa_dma_transfer)
+ labpc_handle_dma_status(dev);
+ else
+ labpc_drain_fifo(dev);
+
+ if (devpriv->stat1 & STAT1_CNTINT) {
+ dev_err(dev->class_dev, "handled timer interrupt?\n");
+ /* clear it */
+ devpriv->write_byte(dev, 0x1, TIMER_CLEAR_REG);
+ }
+
+ if (devpriv->stat1 & STAT1_OVERFLOW) {
+ /* clear error interrupt */
+ devpriv->write_byte(dev, 0x1, ADC_FIFO_CLEAR_REG);
+ async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+ dev_err(dev->class_dev, "overflow\n");
+ return IRQ_HANDLED;
+ }
+ /* handle external stop trigger */
+ if (cmd->stop_src == TRIG_EXT) {
+ if (devpriv->stat2 & STAT2_OUTA1) {
+ labpc_drain_dregs(dev);
+ async->events |= COMEDI_CB_EOA;
+ }
+ }
+
+ /* TRIG_COUNT end of acquisition */
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->count == 0)
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static void labpc_ao_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan, unsigned int val)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ devpriv->write_byte(dev, val & 0xff, DAC_LSB_REG(chan));
+ devpriv->write_byte(dev, (val >> 8) & 0xff, DAC_MSB_REG(chan));
+
+ s->readback[chan] = val;
+}
+
+static int labpc_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ struct labpc_private *devpriv = dev->private;
+ int channel, range;
+ unsigned long flags;
+
+ channel = CR_CHAN(insn->chanspec);
+
+ /* turn off pacing of analog output channel */
+ /* note: hardware bug in daqcard-1200 means pacing cannot
+ * be independently enabled/disabled for its the two channels */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->cmd2 &= ~CMD2_LDAC(channel);
+ devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* set range */
+ if (board->is_labpc1200) {
+ range = CR_RANGE(insn->chanspec);
+ if (comedi_range_is_unipolar(s, range))
+ devpriv->cmd6 |= CMD6_DACUNI(channel);
+ else
+ devpriv->cmd6 &= ~CMD6_DACUNI(channel);
+ /* write to register */
+ devpriv->write_byte(dev, devpriv->cmd6, CMD6_REG);
+ }
+ /* send data */
+ labpc_ao_write(dev, s, channel, data[0]);
+
+ return 1;
+}
+
+/* lowlevel write to eeprom/dac */
+static void labpc_serial_out(struct comedi_device *dev, unsigned int value,
+ unsigned int value_width)
+{
+ struct labpc_private *devpriv = dev->private;
+ int i;
+
+ for (i = 1; i <= value_width; i++) {
+ /* clear serial clock */
+ devpriv->cmd5 &= ~CMD5_SCLK;
+ /* send bits most significant bit first */
+ if (value & (1 << (value_width - i)))
+ devpriv->cmd5 |= CMD5_SDATA;
+ else
+ devpriv->cmd5 &= ~CMD5_SDATA;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ /* set clock to load bit */
+ devpriv->cmd5 |= CMD5_SCLK;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ }
+}
+
+/* lowlevel read from eeprom */
+static unsigned int labpc_serial_in(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int value = 0;
+ int i;
+ const int value_width = 8; /* number of bits wide values are */
+
+ for (i = 1; i <= value_width; i++) {
+ /* set serial clock */
+ devpriv->cmd5 |= CMD5_SCLK;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ /* clear clock bit */
+ devpriv->cmd5 &= ~CMD5_SCLK;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ /* read bits most significant bit first */
+ udelay(1);
+ devpriv->stat2 = devpriv->read_byte(dev, STAT2_REG);
+ if (devpriv->stat2 & STAT2_PROMOUT)
+ value |= 1 << (value_width - i);
+ }
+
+ return value;
+}
+
+static unsigned int labpc_eeprom_read(struct comedi_device *dev,
+ unsigned int address)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int value;
+ /* bits to tell eeprom to expect a read */
+ const int read_instruction = 0x3;
+ /* 8 bit write lengths to eeprom */
+ const int write_length = 8;
+
+ /* enable read/write to eeprom */
+ devpriv->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ /* send read instruction */
+ labpc_serial_out(dev, read_instruction, write_length);
+ /* send 8 bit address to read from */
+ labpc_serial_out(dev, address, write_length);
+ /* read result */
+ value = labpc_serial_in(dev);
+
+ /* disable read/write to eeprom */
+ devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ return value;
+}
+
+static unsigned int labpc_eeprom_read_status(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int value;
+ const int read_status_instruction = 0x5;
+ const int write_length = 8; /* 8 bit write lengths to eeprom */
+
+ /* enable read/write to eeprom */
+ devpriv->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ /* send read status instruction */
+ labpc_serial_out(dev, read_status_instruction, write_length);
+ /* read result */
+ value = labpc_serial_in(dev);
+
+ /* disable read/write to eeprom */
+ devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ return value;
+}
+
+static void labpc_eeprom_write(struct comedi_device *dev,
+ unsigned int address, unsigned int value)
+{
+ struct labpc_private *devpriv = dev->private;
+ const int write_enable_instruction = 0x6;
+ const int write_instruction = 0x2;
+ const int write_length = 8; /* 8 bit write lengths to eeprom */
+
+ /* enable read/write to eeprom */
+ devpriv->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ /* send write_enable instruction */
+ labpc_serial_out(dev, write_enable_instruction, write_length);
+ devpriv->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ /* send write instruction */
+ devpriv->cmd5 |= CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ labpc_serial_out(dev, write_instruction, write_length);
+ /* send 8 bit address to write to */
+ labpc_serial_out(dev, address, write_length);
+ /* write value */
+ labpc_serial_out(dev, value, write_length);
+ devpriv->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ /* disable read/write to eeprom */
+ devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+}
+
+/* writes to 8 bit calibration dacs */
+static void write_caldac(struct comedi_device *dev, unsigned int channel,
+ unsigned int value)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ /* clear caldac load bit and make sure we don't write to eeprom */
+ devpriv->cmd5 &= ~(CMD5_CALDACLD | CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+
+ /* write 4 bit channel */
+ labpc_serial_out(dev, channel, 4);
+ /* write 8 bit caldac value */
+ labpc_serial_out(dev, value, 8);
+
+ /* set and clear caldac bit to load caldac value */
+ devpriv->cmd5 |= CMD5_CALDACLD;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ devpriv->cmd5 &= ~CMD5_CALDACLD;
+ udelay(1);
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+}
+
+static int labpc_calib_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * Only write the last data value to the caldac. Preceding
+ * data would be overwritten anyway.
+ */
+ if (insn->n > 0) {
+ unsigned int val = data[insn->n - 1];
+
+ if (s->readback[chan] != val) {
+ write_caldac(dev, chan, val);
+ s->readback[chan] = val;
+ }
+ }
+
+ return insn->n;
+}
+
+static int labpc_eeprom_ready(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ /* make sure there isn't already a write in progress */
+ status = labpc_eeprom_read_status(dev);
+ if ((status & 0x1) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int labpc_eeprom_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret;
+
+ /* only allow writes to user area of eeprom */
+ if (chan < 16 || chan > 127)
+ return -EINVAL;
+
+ /*
+ * Only write the last data value to the eeprom. Preceding
+ * data would be overwritten anyway.
+ */
+ if (insn->n > 0) {
+ unsigned int val = data[insn->n - 1];
+
+ ret = comedi_timeout(dev, s, insn, labpc_eeprom_ready, 0);
+ if (ret)
+ return ret;
+
+ labpc_eeprom_write(dev, chan, val);
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+int labpc_common_attach(struct comedi_device *dev,
+ unsigned int irq, unsigned long isr_flags)
+{
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ struct labpc_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ if (dev->mmio) {
+ devpriv->read_byte = labpc_readb;
+ devpriv->write_byte = labpc_writeb;
+ } else {
+ devpriv->read_byte = labpc_inb;
+ devpriv->write_byte = labpc_outb;
+ }
+
+ /* initialize board's command registers */
+ devpriv->write_byte(dev, devpriv->cmd1, CMD1_REG);
+ devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG);
+ devpriv->write_byte(dev, devpriv->cmd3, CMD3_REG);
+ devpriv->write_byte(dev, devpriv->cmd4, CMD4_REG);
+ if (board->is_labpc1200) {
+ devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG);
+ devpriv->write_byte(dev, devpriv->cmd6, CMD6_REG);
+ }
+
+ if (irq) {
+ ret = request_irq(irq, labpc_interrupt, isr_flags,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ if (dev->mmio) {
+ dev->pacer = comedi_8254_mm_init(dev->mmio + COUNTER_B_BASE_REG,
+ I8254_OSC_BASE_2MHZ,
+ I8254_IO8, 0);
+ devpriv->counter = comedi_8254_mm_init(dev->mmio +
+ COUNTER_A_BASE_REG,
+ I8254_OSC_BASE_2MHZ,
+ I8254_IO8, 0);
+ } else {
+ dev->pacer = comedi_8254_init(dev->iobase + COUNTER_B_BASE_REG,
+ I8254_OSC_BASE_2MHZ,
+ I8254_IO8, 0);
+ devpriv->counter = comedi_8254_init(dev->iobase +
+ COUNTER_A_BASE_REG,
+ I8254_OSC_BASE_2MHZ,
+ I8254_IO8, 0);
+ }
+ if (!dev->pacer || !devpriv->counter)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;
+ s->n_chan = 8;
+ s->len_chanlist = 8;
+ s->maxdata = 0x0fff;
+ s->range_table = board->is_labpc1200 ?
+ &range_labpc_1200_ai : &range_labpc_plus_ai;
+ s->insn_read = labpc_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmd = labpc_ai_cmd;
+ s->do_cmdtest = labpc_ai_cmdtest;
+ s->cancel = labpc_cancel;
+ }
+
+ /* analog output */
+ s = &dev->subdevices[1];
+ if (board->has_ao) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = NUM_AO_CHAN;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_labpc_ao;
+ s->insn_write = labpc_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* initialize analog outputs to a known value */
+ for (i = 0; i < s->n_chan; i++)
+ labpc_ao_write(dev, s, i, s->maxdata / 2);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* 8255 dio */
+ s = &dev->subdevices[2];
+ if (dev->mmio)
+ ret = subdev_8255_mm_init(dev, s, NULL, DIO_BASE_REG);
+ else
+ ret = subdev_8255_init(dev, s, NULL, DIO_BASE_REG);
+ if (ret)
+ return ret;
+
+ /* calibration subdevices for boards that have one */
+ s = &dev->subdevices[3];
+ if (board->is_labpc1200) {
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = 16;
+ s->maxdata = 0xff;
+ s->insn_write = labpc_calib_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++) {
+ write_caldac(dev, i, s->maxdata / 2);
+ s->readback[i] = s->maxdata / 2;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* EEPROM */
+ s = &dev->subdevices[4];
+ if (board->is_labpc1200) {
+ s->type = COMEDI_SUBD_MEMORY;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = EEPROM_SIZE;
+ s->maxdata = 0xff;
+ s->insn_write = labpc_eeprom_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < s->n_chan; i++)
+ s->readback[i] = labpc_eeprom_read(dev, i);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(labpc_common_attach);
+
+void labpc_common_detach(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ if (devpriv)
+ kfree(devpriv->counter);
+}
+EXPORT_SYMBOL_GPL(labpc_common_detach);
+
+static int __init labpc_common_init(void)
+{
+ return 0;
+}
+module_init(labpc_common_init);
+
+static void __exit labpc_common_exit(void)
+{
+}
+module_exit(labpc_common_exit);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi helper for ni_labpc, ni_labpc_pci, ni_labpc_cs");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc_cs.c b/drivers/staging/comedi/drivers/ni_labpc_cs.c
new file mode 100644
index 000000000..a1c69ac07
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_cs.c
@@ -0,0 +1,128 @@
+/*
+ comedi/drivers/ni_labpc_cs.c
+ Driver for National Instruments daqcard-1200 boards
+ Copyright (C) 2001, 2002, 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ PCMCIA crap is adapted from dummy_cs.c 1.31 2001/08/24 12:13:13
+ from the pcmcia package.
+ The initial developer of the pcmcia dummy_cs.c code is David A. Hinds
+ <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
+ are Copyright (C) 1999 David A. Hinds.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_labpc_cs
+Description: National Instruments Lab-PC (& compatibles)
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+Devices: [National Instruments] DAQCard-1200 (daqcard-1200)
+Status: works
+
+Thanks go to Fredrik Lingvall for much testing and perseverance in
+helping to debug daqcard-1200 support.
+
+The 1200 series boards have onboard calibration dacs for correcting
+analog input/output offsets and gains. The proper settings for these
+caldacs are stored on the board's eeprom. To read the caldac values
+from the eeprom and store them into a file that can be then be used by
+comedilib, use the comedi_calibrate program.
+
+Configuration options:
+ none
+
+The daqcard-1200 has quirky chanlist requirements
+when scanning multiple channels. Multiple channel scan
+sequence must start at highest channel, then decrement down to
+channel 0. Chanlists consisting of all one channel
+are also legal, and allow you to pace conversions in bursts.
+
+*/
+
+/*
+
+NI manuals:
+340988a (daqcard-1200)
+
+*/
+
+#include <linux/module.h>
+
+#include "../comedi_pcmcia.h"
+
+#include "ni_labpc.h"
+
+static const struct labpc_boardinfo labpc_cs_boards[] = {
+ {
+ .name = "daqcard-1200",
+ .ai_speed = 10000,
+ .has_ao = 1,
+ .is_labpc1200 = 1,
+ },
+};
+
+static int labpc_cs_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ int ret;
+
+ /* The ni_labpc driver needs the board_ptr */
+ dev->board_ptr = &labpc_cs_boards[0];
+
+ link->config_flags |= CONF_AUTO_SET_IO |
+ CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ if (!link->irq)
+ return -EINVAL;
+
+ return labpc_common_attach(dev, link->irq, IRQF_SHARED);
+}
+
+static void labpc_cs_detach(struct comedi_device *dev)
+{
+ labpc_common_detach(dev);
+ comedi_pcmcia_disable(dev);
+}
+
+static struct comedi_driver driver_labpc_cs = {
+ .driver_name = "ni_labpc_cs",
+ .module = THIS_MODULE,
+ .auto_attach = labpc_cs_auto_attach,
+ .detach = labpc_cs_detach,
+};
+
+static int labpc_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_labpc_cs);
+}
+
+static const struct pcmcia_device_id labpc_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0103), /* daqcard-1200 */
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, labpc_cs_ids);
+
+static struct pcmcia_driver labpc_cs_driver = {
+ .name = "daqcard-1200",
+ .owner = THIS_MODULE,
+ .id_table = labpc_cs_ids,
+ .probe = labpc_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_labpc_cs, labpc_cs_driver);
+
+MODULE_DESCRIPTION("Comedi driver for National Instruments Lab-PC");
+MODULE_AUTHOR("Frank Mori Hess <fmhess@users.sourceforge.net>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc_isadma.c b/drivers/staging/comedi/drivers/ni_labpc_isadma.c
new file mode 100644
index 000000000..29dbdf5ec
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_isadma.c
@@ -0,0 +1,190 @@
+/*
+ * comedi/drivers/ni_labpc_isadma.c
+ * ISA DMA support for National Instruments Lab-PC series boards and
+ * compatibles.
+ *
+ * Extracted from ni_labpc.c:
+ * Copyright (C) 2001-2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "ni_labpc.h"
+#include "ni_labpc_regs.h"
+#include "ni_labpc_isadma.h"
+
+/* size in bytes of dma buffer */
+#define LABPC_ISADMA_BUFFER_SIZE 0xff00
+
+/* utility function that suggests a dma transfer size in bytes */
+static unsigned int labpc_suggest_transfer_size(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int maxbytes)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int sample_size = comedi_bytes_per_sample(s);
+ unsigned int size;
+ unsigned int freq;
+
+ if (cmd->convert_src == TRIG_TIMER)
+ freq = 1000000000 / cmd->convert_arg;
+ else
+ /* return some default value */
+ freq = 0xffffffff;
+
+ /* make buffer fill in no more than 1/3 second */
+ size = (freq / 3) * sample_size;
+
+ /* set a minimum and maximum size allowed */
+ if (size > maxbytes)
+ size = maxbytes;
+ else if (size < sample_size)
+ size = sample_size;
+
+ return size;
+}
+
+void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int sample_size = comedi_bytes_per_sample(s);
+
+ /* set appropriate size of transfer */
+ desc->size = labpc_suggest_transfer_size(dev, s, desc->maxsize);
+ if (cmd->stop_src == TRIG_COUNT &&
+ devpriv->count * sample_size < desc->size)
+ desc->size = devpriv->count * sample_size;
+
+ comedi_isadma_program(desc);
+
+ /* set CMD3 bits for caller to enable DMA and interrupt */
+ devpriv->cmd3 |= (CMD3_DMAEN | CMD3_DMATCINTEN);
+}
+EXPORT_SYMBOL_GPL(labpc_setup_dma);
+
+void labpc_drain_dma(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int max_samples = comedi_bytes_to_samples(s, desc->size);
+ unsigned int residue;
+ unsigned int nsamples;
+ unsigned int leftover;
+
+ /*
+ * residue is the number of bytes left to be done on the dma
+ * transfer. It should always be zero at this point unless
+ * the stop_src is set to external triggering.
+ */
+ residue = comedi_isadma_disable(desc->chan);
+
+ /*
+ * Figure out how many samples to read for this transfer and
+ * how many will be stored for next time.
+ */
+ nsamples = max_samples - comedi_bytes_to_samples(s, residue);
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->count <= nsamples) {
+ nsamples = devpriv->count;
+ leftover = 0;
+ } else {
+ leftover = devpriv->count - nsamples;
+ if (leftover > max_samples)
+ leftover = max_samples;
+ }
+ devpriv->count -= nsamples;
+ } else {
+ leftover = max_samples;
+ }
+ desc->size = comedi_samples_to_bytes(s, leftover);
+
+ comedi_buf_write_samples(s, desc->virt_addr, nsamples);
+}
+EXPORT_SYMBOL_GPL(labpc_drain_dma);
+
+static void handle_isa_dma(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
+
+ labpc_drain_dma(dev);
+
+ if (desc->size)
+ comedi_isadma_program(desc);
+
+ /* clear dma tc interrupt */
+ devpriv->write_byte(dev, 0x1, DMATC_CLEAR_REG);
+}
+
+void labpc_handle_dma_status(struct comedi_device *dev)
+{
+ const struct labpc_boardinfo *board = dev->board_ptr;
+ struct labpc_private *devpriv = dev->private;
+
+ /*
+ * if a dma terminal count of external stop trigger
+ * has occurred
+ */
+ if (devpriv->stat1 & STAT1_GATA0 ||
+ (board->is_labpc1200 && devpriv->stat2 & STAT2_OUTA1))
+ handle_isa_dma(dev);
+}
+EXPORT_SYMBOL_GPL(labpc_handle_dma_status);
+
+void labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ /* only DMA channels 3 and 1 are valid */
+ if (dma_chan != 1 && dma_chan != 3)
+ return;
+
+ /* DMA uses 1 buffer */
+ devpriv->dma = comedi_isadma_alloc(dev, 1, dma_chan, dma_chan,
+ LABPC_ISADMA_BUFFER_SIZE,
+ COMEDI_ISADMA_READ);
+}
+EXPORT_SYMBOL_GPL(labpc_init_dma_chan);
+
+void labpc_free_dma_chan(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ if (devpriv)
+ comedi_isadma_free(devpriv->dma);
+}
+EXPORT_SYMBOL_GPL(labpc_free_dma_chan);
+
+static int __init ni_labpc_isadma_init_module(void)
+{
+ return 0;
+}
+module_init(ni_labpc_isadma_init_module);
+
+static void __exit ni_labpc_isadma_cleanup_module(void)
+{
+}
+module_exit(ni_labpc_isadma_cleanup_module);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi NI Lab-PC ISA DMA support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc_isadma.h b/drivers/staging/comedi/drivers/ni_labpc_isadma.h
new file mode 100644
index 000000000..b8a1b0ee6
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_isadma.h
@@ -0,0 +1,42 @@
+/*
+ * ni_labpc ISA DMA support.
+*/
+
+#ifndef _NI_LABPC_ISADMA_H
+#define _NI_LABPC_ISADMA_H
+
+#if IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISADMA)
+
+void labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan);
+void labpc_free_dma_chan(struct comedi_device *dev);
+void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s);
+void labpc_drain_dma(struct comedi_device *dev);
+void labpc_handle_dma_status(struct comedi_device *dev);
+
+#else
+
+static inline void labpc_init_dma_chan(struct comedi_device *dev,
+ unsigned int dma_chan)
+{
+}
+
+static inline void labpc_free_dma_chan(struct comedi_device *dev)
+{
+}
+
+static inline void labpc_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+}
+
+static inline void labpc_drain_dma(struct comedi_device *dev)
+{
+}
+
+static inline void labpc_handle_dma_status(struct comedi_device *dev)
+{
+}
+
+#endif
+
+#endif /* _NI_LABPC_ISADMA_H */
diff --git a/drivers/staging/comedi/drivers/ni_labpc_pci.c b/drivers/staging/comedi/drivers/ni_labpc_pci.c
new file mode 100644
index 000000000..77d403801
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_pci.c
@@ -0,0 +1,141 @@
+/*
+ * comedi/drivers/ni_labpc_pci.c
+ * Driver for National Instruments Lab-PC PCI-1200
+ * Copyright (C) 2001, 2002, 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_labpc_pci
+ * Description: National Instruments Lab-PC PCI-1200
+ * Devices: [National Instruments] PCI-1200 (ni_pci-1200)
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ *
+ * This is the PCI-specific support split off from the ni_labpc driver.
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ *
+ * NI manuals:
+ * 340914a (pci-1200)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "ni_labpc.h"
+
+enum labpc_pci_boardid {
+ BOARD_NI_PCI1200,
+};
+
+static const struct labpc_boardinfo labpc_pci_boards[] = {
+ [BOARD_NI_PCI1200] = {
+ .name = "ni_pci-1200",
+ .ai_speed = 10000,
+ .ai_scan_up = 1,
+ .has_ao = 1,
+ .is_labpc1200 = 1,
+ },
+};
+
+/* ripped from mite.h and mite_setup2() to avoid mite dependency */
+#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */
+#define WENAB (1 << 7) /* window enable */
+
+static int labpc_pci_mite_init(struct pci_dev *pcidev)
+{
+ void __iomem *mite_base;
+ u32 main_phys_addr;
+
+ /* ioremap the MITE registers (BAR 0) temporarily */
+ mite_base = pci_ioremap_bar(pcidev, 0);
+ if (!mite_base)
+ return -ENOMEM;
+
+ /* set data window to main registers (BAR 1) */
+ main_phys_addr = pci_resource_start(pcidev, 1);
+ writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR);
+
+ /* finished with MITE registers */
+ iounmap(mite_base);
+ return 0;
+}
+
+static int labpc_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct labpc_boardinfo *board = NULL;
+ int ret;
+
+ if (context < ARRAY_SIZE(labpc_pci_boards))
+ board = &labpc_pci_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = labpc_pci_mite_init(pcidev);
+ if (ret)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 1);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ return labpc_common_attach(dev, pcidev->irq, IRQF_SHARED);
+}
+
+static void labpc_pci_detach(struct comedi_device *dev)
+{
+ labpc_common_detach(dev);
+ comedi_pci_detach(dev);
+}
+
+static struct comedi_driver labpc_pci_comedi_driver = {
+ .driver_name = "labpc_pci",
+ .module = THIS_MODULE,
+ .auto_attach = labpc_pci_auto_attach,
+ .detach = labpc_pci_detach,
+};
+
+static const struct pci_device_id labpc_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x161), BOARD_NI_PCI1200 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, labpc_pci_table);
+
+static int labpc_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &labpc_pci_comedi_driver,
+ id->driver_data);
+}
+
+static struct pci_driver labpc_pci_driver = {
+ .name = "labpc_pci",
+ .id_table = labpc_pci_table,
+ .probe = labpc_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(labpc_pci_comedi_driver, labpc_pci_driver);
+
+MODULE_DESCRIPTION("Comedi: National Instruments Lab-PC PCI-1200 driver");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc_regs.h b/drivers/staging/comedi/drivers/ni_labpc_regs.h
new file mode 100644
index 000000000..2a274a3e4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_regs.h
@@ -0,0 +1,75 @@
+/*
+ * ni_labpc register definitions.
+*/
+
+#ifndef _NI_LABPC_REGS_H
+#define _NI_LABPC_REGS_H
+
+/*
+ * Register map (all registers are 8-bit)
+ */
+#define STAT1_REG 0x00 /* R: Status 1 reg */
+#define STAT1_DAVAIL (1 << 0)
+#define STAT1_OVERRUN (1 << 1)
+#define STAT1_OVERFLOW (1 << 2)
+#define STAT1_CNTINT (1 << 3)
+#define STAT1_GATA0 (1 << 5)
+#define STAT1_EXTGATA0 (1 << 6)
+#define CMD1_REG 0x00 /* W: Command 1 reg */
+#define CMD1_MA(x) (((x) & 0x7) << 0)
+#define CMD1_TWOSCMP (1 << 3)
+#define CMD1_GAIN(x) (((x) & 0x7) << 4)
+#define CMD1_SCANEN (1 << 7)
+#define CMD2_REG 0x01 /* W: Command 2 reg */
+#define CMD2_PRETRIG (1 << 0)
+#define CMD2_HWTRIG (1 << 1)
+#define CMD2_SWTRIG (1 << 2)
+#define CMD2_TBSEL (1 << 3)
+#define CMD2_2SDAC0 (1 << 4)
+#define CMD2_2SDAC1 (1 << 5)
+#define CMD2_LDAC(x) (1 << (6 + (x)))
+#define CMD3_REG 0x02 /* W: Command 3 reg */
+#define CMD3_DMAEN (1 << 0)
+#define CMD3_DIOINTEN (1 << 1)
+#define CMD3_DMATCINTEN (1 << 2)
+#define CMD3_CNTINTEN (1 << 3)
+#define CMD3_ERRINTEN (1 << 4)
+#define CMD3_FIFOINTEN (1 << 5)
+#define ADC_START_CONVERT_REG 0x03 /* W: Start Convert reg */
+#define DAC_LSB_REG(x) (0x04 + 2 * (x)) /* W: DAC0/1 LSB reg */
+#define DAC_MSB_REG(x) (0x05 + 2 * (x)) /* W: DAC0/1 MSB reg */
+#define ADC_FIFO_CLEAR_REG 0x08 /* W: A/D FIFO Clear reg */
+#define ADC_FIFO_REG 0x0a /* R: A/D FIFO reg */
+#define DMATC_CLEAR_REG 0x0a /* W: DMA Interrupt Clear reg */
+#define TIMER_CLEAR_REG 0x0c /* W: Timer Interrupt Clear reg */
+#define CMD6_REG 0x0e /* W: Command 6 reg */
+#define CMD6_NRSE (1 << 0)
+#define CMD6_ADCUNI (1 << 1)
+#define CMD6_DACUNI(x) (1 << (2 + (x)))
+#define CMD6_HFINTEN (1 << 5)
+#define CMD6_DQINTEN (1 << 6)
+#define CMD6_SCANUP (1 << 7)
+#define CMD4_REG 0x0f /* W: Command 3 reg */
+#define CMD4_INTSCAN (1 << 0)
+#define CMD4_EOIRCV (1 << 1)
+#define CMD4_ECLKDRV (1 << 2)
+#define CMD4_SEDIFF (1 << 3)
+#define CMD4_ECLKRCV (1 << 4)
+#define DIO_BASE_REG 0x10 /* R/W: 8255 DIO base reg */
+#define COUNTER_A_BASE_REG 0x14 /* R/W: 8253 Counter A base reg */
+#define COUNTER_B_BASE_REG 0x18 /* R/W: 8253 Counter B base reg */
+#define CMD5_REG 0x1c /* W: Command 5 reg */
+#define CMD5_WRTPRT (1 << 2)
+#define CMD5_DITHEREN (1 << 3)
+#define CMD5_CALDACLD (1 << 4)
+#define CMD5_SCLK (1 << 5)
+#define CMD5_SDATA (1 << 6)
+#define CMD5_EEPROMCS (1 << 7)
+#define STAT2_REG 0x1d /* R: Status 2 reg */
+#define STAT2_PROMOUT (1 << 0)
+#define STAT2_OUTA1 (1 << 1)
+#define STAT2_FIFONHF (1 << 2)
+#define INTERVAL_COUNT_REG 0x1e /* W: Interval Counter Data reg */
+#define INTERVAL_STROBE_REG 0x1f /* W: Interval Counter Strobe reg */
+
+#endif /* _NI_LABPC_REGS_H */
diff --git a/drivers/staging/comedi/drivers/ni_mio_common.c b/drivers/staging/comedi/drivers/ni_mio_common.c
new file mode 100644
index 000000000..c66affd99
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_mio_common.c
@@ -0,0 +1,5703 @@
+/*
+ comedi/drivers/ni_mio_common.c
+ Hardware driver for DAQ-STC based boards
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2001 David A. Schleef <ds@schleef.org>
+ Copyright (C) 2002-2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+ This file is meant to be included by another file, e.g.,
+ ni_atmio.c or ni_pcimio.c.
+
+ Interrupt support originally added by Truxton Fulton
+ <trux@truxton.com>
+
+ References (from ftp://ftp.natinst.com/support/manuals):
+
+ 340747b.pdf AT-MIO E series Register Level Programmer Manual
+ 341079b.pdf PCI E Series RLPM
+ 340934b.pdf DAQ-STC reference manual
+ 67xx and 611x registers (from ftp://ftp.ni.com/support/daq/mhddk/documentation/)
+ release_ni611x.pdf
+ release_ni67xx.pdf
+ Other possibly relevant info:
+
+ 320517c.pdf User manual (obsolete)
+ 320517f.pdf User manual (new)
+ 320889a.pdf delete
+ 320906c.pdf maximum signal ratings
+ 321066a.pdf about 16x
+ 321791a.pdf discontinuation of at-mio-16e-10 rev. c
+ 321808a.pdf about at-mio-16e-10 rev P
+ 321837a.pdf discontinuation of at-mio-16de-10 rev d
+ 321838a.pdf about at-mio-16de-10 rev N
+
+ ISSUES:
+
+ - the interrupt routine needs to be cleaned up
+
+ 2006-02-07: S-Series PCI-6143: Support has been added but is not
+ fully tested as yet. Terry Barnaby, BEAM Ltd.
+*/
+
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include "8255.h"
+#include "mite.h"
+
+/* A timeout count */
+#define NI_TIMEOUT 1000
+static const unsigned old_RTSI_clock_channel = 7;
+
+/* Note: this table must match the ai_gain_* definitions */
+static const short ni_gainlkup[][16] = {
+ [ai_gain_16] = {0, 1, 2, 3, 4, 5, 6, 7,
+ 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107},
+ [ai_gain_8] = {1, 2, 4, 7, 0x101, 0x102, 0x104, 0x107},
+ [ai_gain_14] = {1, 2, 3, 4, 5, 6, 7,
+ 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107},
+ [ai_gain_4] = {0, 1, 4, 7},
+ [ai_gain_611x] = {0x00a, 0x00b, 0x001, 0x002,
+ 0x003, 0x004, 0x005, 0x006},
+ [ai_gain_622x] = {0, 1, 4, 5},
+ [ai_gain_628x] = {1, 2, 3, 4, 5, 6, 7},
+ [ai_gain_6143] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+};
+
+static const struct comedi_lrange range_ni_E_ai = {
+ 16, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.25),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(20),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.2),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_limited = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_limited14 = {
+ 14, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.2),
+ BIP_RANGE(0.1),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.2),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_bipolar4 = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_611x = {
+ 8, {
+ BIP_RANGE(50),
+ BIP_RANGE(20),
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.2)
+ }
+};
+
+static const struct comedi_lrange range_ni_M_ai_622x = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.2)
+ }
+};
+
+static const struct comedi_lrange range_ni_M_ai_628x = {
+ 7, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.2),
+ BIP_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ao_ext = {
+ 4, {
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ RANGE_ext(-1, 1),
+ RANGE_ext(0, 1)
+ }
+};
+
+static const struct comedi_lrange *const ni_range_lkup[] = {
+ [ai_gain_16] = &range_ni_E_ai,
+ [ai_gain_8] = &range_ni_E_ai_limited,
+ [ai_gain_14] = &range_ni_E_ai_limited14,
+ [ai_gain_4] = &range_ni_E_ai_bipolar4,
+ [ai_gain_611x] = &range_ni_E_ai_611x,
+ [ai_gain_622x] = &range_ni_M_ai_622x,
+ [ai_gain_628x] = &range_ni_M_ai_628x,
+ [ai_gain_6143] = &range_bipolar5
+};
+
+enum aimodes {
+ AIMODE_NONE = 0,
+ AIMODE_HALF_FULL = 1,
+ AIMODE_SCAN = 2,
+ AIMODE_SAMPLE = 3,
+};
+
+enum ni_common_subdevices {
+ NI_AI_SUBDEV,
+ NI_AO_SUBDEV,
+ NI_DIO_SUBDEV,
+ NI_8255_DIO_SUBDEV,
+ NI_UNUSED_SUBDEV,
+ NI_CALIBRATION_SUBDEV,
+ NI_EEPROM_SUBDEV,
+ NI_PFI_DIO_SUBDEV,
+ NI_CS5529_CALIBRATION_SUBDEV,
+ NI_SERIAL_SUBDEV,
+ NI_RTSI_SUBDEV,
+ NI_GPCT0_SUBDEV,
+ NI_GPCT1_SUBDEV,
+ NI_FREQ_OUT_SUBDEV,
+ NI_NUM_SUBDEVICES
+};
+static inline unsigned NI_GPCT_SUBDEV(unsigned counter_index)
+{
+ switch (counter_index) {
+ case 0:
+ return NI_GPCT0_SUBDEV;
+ case 1:
+ return NI_GPCT1_SUBDEV;
+ default:
+ break;
+ }
+ BUG();
+ return NI_GPCT0_SUBDEV;
+}
+
+enum timebase_nanoseconds {
+ TIMEBASE_1_NS = 50,
+ TIMEBASE_2_NS = 10000
+};
+
+#define SERIAL_DISABLED 0
+#define SERIAL_600NS 600
+#define SERIAL_1_2US 1200
+#define SERIAL_10US 10000
+
+static const int num_adc_stages_611x = 3;
+
+static void ni_writel(struct comedi_device *dev, uint32_t data, int reg)
+{
+ if (dev->mmio)
+ writel(data, dev->mmio + reg);
+
+ outl(data, dev->iobase + reg);
+}
+
+static void ni_writew(struct comedi_device *dev, uint16_t data, int reg)
+{
+ if (dev->mmio)
+ writew(data, dev->mmio + reg);
+
+ outw(data, dev->iobase + reg);
+}
+
+static void ni_writeb(struct comedi_device *dev, uint8_t data, int reg)
+{
+ if (dev->mmio)
+ writeb(data, dev->mmio + reg);
+
+ outb(data, dev->iobase + reg);
+}
+
+static uint32_t ni_readl(struct comedi_device *dev, int reg)
+{
+ if (dev->mmio)
+ return readl(dev->mmio + reg);
+
+ return inl(dev->iobase + reg);
+}
+
+static uint16_t ni_readw(struct comedi_device *dev, int reg)
+{
+ if (dev->mmio)
+ return readw(dev->mmio + reg);
+
+ return inw(dev->iobase + reg);
+}
+
+static uint8_t ni_readb(struct comedi_device *dev, int reg)
+{
+ if (dev->mmio)
+ return readb(dev->mmio + reg);
+
+ return inb(dev->iobase + reg);
+}
+
+/*
+ * We automatically take advantage of STC registers that can be
+ * read/written directly in the I/O space of the board.
+ *
+ * The AT-MIO and DAQCard devices map the low 8 STC registers to
+ * iobase+reg*2.
+ *
+ * Most PCIMIO devices also map the low 8 STC registers but the
+ * 611x devices map the read registers to iobase+(addr-1)*2.
+ * For now non-windowed STC access is disabled if a PCIMIO device
+ * is detected (devpriv->mite has been initialized).
+ *
+ * The M series devices do not used windowed registers for the
+ * STC registers. The functions below handle the mapping of the
+ * windowed STC registers to the m series register offsets.
+ */
+
+static void m_series_stc_writel(struct comedi_device *dev,
+ uint32_t data, int reg)
+{
+ unsigned offset;
+
+ switch (reg) {
+ case AI_SC_Load_A_Registers:
+ offset = M_Offset_AI_SC_Load_A;
+ break;
+ case AI_SI_Load_A_Registers:
+ offset = M_Offset_AI_SI_Load_A;
+ break;
+ case AO_BC_Load_A_Register:
+ offset = M_Offset_AO_BC_Load_A;
+ break;
+ case AO_UC_Load_A_Register:
+ offset = M_Offset_AO_UC_Load_A;
+ break;
+ case AO_UI_Load_A_Register:
+ offset = M_Offset_AO_UI_Load_A;
+ break;
+ case G_Load_A_Register(0):
+ offset = M_Offset_G0_Load_A;
+ break;
+ case G_Load_A_Register(1):
+ offset = M_Offset_G1_Load_A;
+ break;
+ case G_Load_B_Register(0):
+ offset = M_Offset_G0_Load_B;
+ break;
+ case G_Load_B_Register(1):
+ offset = M_Offset_G1_Load_B;
+ break;
+ default:
+ dev_warn(dev->class_dev,
+ "%s: bug! unhandled register=0x%x in switch\n",
+ __func__, reg);
+ return;
+ }
+ ni_writel(dev, data, offset);
+}
+
+static void m_series_stc_writew(struct comedi_device *dev,
+ uint16_t data, int reg)
+{
+ unsigned offset;
+
+ switch (reg) {
+ case ADC_FIFO_Clear:
+ offset = M_Offset_AI_FIFO_Clear;
+ break;
+ case AI_Command_1_Register:
+ offset = M_Offset_AI_Command_1;
+ break;
+ case AI_Command_2_Register:
+ offset = M_Offset_AI_Command_2;
+ break;
+ case AI_Mode_1_Register:
+ offset = M_Offset_AI_Mode_1;
+ break;
+ case AI_Mode_2_Register:
+ offset = M_Offset_AI_Mode_2;
+ break;
+ case AI_Mode_3_Register:
+ offset = M_Offset_AI_Mode_3;
+ break;
+ case AI_Output_Control_Register:
+ offset = M_Offset_AI_Output_Control;
+ break;
+ case AI_Personal_Register:
+ offset = M_Offset_AI_Personal;
+ break;
+ case AI_SI2_Load_A_Register:
+ /* this is a 32 bit register on m series boards */
+ ni_writel(dev, data, M_Offset_AI_SI2_Load_A);
+ return;
+ case AI_SI2_Load_B_Register:
+ /* this is a 32 bit register on m series boards */
+ ni_writel(dev, data, M_Offset_AI_SI2_Load_B);
+ return;
+ case AI_START_STOP_Select_Register:
+ offset = M_Offset_AI_START_STOP_Select;
+ break;
+ case AI_Trigger_Select_Register:
+ offset = M_Offset_AI_Trigger_Select;
+ break;
+ case Analog_Trigger_Etc_Register:
+ offset = M_Offset_Analog_Trigger_Etc;
+ break;
+ case AO_Command_1_Register:
+ offset = M_Offset_AO_Command_1;
+ break;
+ case AO_Command_2_Register:
+ offset = M_Offset_AO_Command_2;
+ break;
+ case AO_Mode_1_Register:
+ offset = M_Offset_AO_Mode_1;
+ break;
+ case AO_Mode_2_Register:
+ offset = M_Offset_AO_Mode_2;
+ break;
+ case AO_Mode_3_Register:
+ offset = M_Offset_AO_Mode_3;
+ break;
+ case AO_Output_Control_Register:
+ offset = M_Offset_AO_Output_Control;
+ break;
+ case AO_Personal_Register:
+ offset = M_Offset_AO_Personal;
+ break;
+ case AO_Start_Select_Register:
+ offset = M_Offset_AO_Start_Select;
+ break;
+ case AO_Trigger_Select_Register:
+ offset = M_Offset_AO_Trigger_Select;
+ break;
+ case Clock_and_FOUT_Register:
+ offset = M_Offset_Clock_and_FOUT;
+ break;
+ case Configuration_Memory_Clear:
+ offset = M_Offset_Configuration_Memory_Clear;
+ break;
+ case DAC_FIFO_Clear:
+ offset = M_Offset_AO_FIFO_Clear;
+ break;
+ case DIO_Control_Register:
+ dev_dbg(dev->class_dev,
+ "%s: FIXME: register 0x%x does not map cleanly on to m-series boards\n",
+ __func__, reg);
+ return;
+ case G_Autoincrement_Register(0):
+ offset = M_Offset_G0_Autoincrement;
+ break;
+ case G_Autoincrement_Register(1):
+ offset = M_Offset_G1_Autoincrement;
+ break;
+ case G_Command_Register(0):
+ offset = M_Offset_G0_Command;
+ break;
+ case G_Command_Register(1):
+ offset = M_Offset_G1_Command;
+ break;
+ case G_Input_Select_Register(0):
+ offset = M_Offset_G0_Input_Select;
+ break;
+ case G_Input_Select_Register(1):
+ offset = M_Offset_G1_Input_Select;
+ break;
+ case G_Mode_Register(0):
+ offset = M_Offset_G0_Mode;
+ break;
+ case G_Mode_Register(1):
+ offset = M_Offset_G1_Mode;
+ break;
+ case Interrupt_A_Ack_Register:
+ offset = M_Offset_Interrupt_A_Ack;
+ break;
+ case Interrupt_A_Enable_Register:
+ offset = M_Offset_Interrupt_A_Enable;
+ break;
+ case Interrupt_B_Ack_Register:
+ offset = M_Offset_Interrupt_B_Ack;
+ break;
+ case Interrupt_B_Enable_Register:
+ offset = M_Offset_Interrupt_B_Enable;
+ break;
+ case Interrupt_Control_Register:
+ offset = M_Offset_Interrupt_Control;
+ break;
+ case IO_Bidirection_Pin_Register:
+ offset = M_Offset_IO_Bidirection_Pin;
+ break;
+ case Joint_Reset_Register:
+ offset = M_Offset_Joint_Reset;
+ break;
+ case RTSI_Trig_A_Output_Register:
+ offset = M_Offset_RTSI_Trig_A_Output;
+ break;
+ case RTSI_Trig_B_Output_Register:
+ offset = M_Offset_RTSI_Trig_B_Output;
+ break;
+ case RTSI_Trig_Direction_Register:
+ offset = M_Offset_RTSI_Trig_Direction;
+ break;
+ /*
+ * FIXME: DIO_Output_Register (16 bit reg) is replaced by
+ * M_Offset_Static_Digital_Output (32 bit) and
+ * M_Offset_SCXI_Serial_Data_Out (8 bit)
+ */
+ default:
+ dev_warn(dev->class_dev,
+ "%s: bug! unhandled register=0x%x in switch\n",
+ __func__, reg);
+ return;
+ }
+ ni_writew(dev, data, offset);
+}
+
+static uint32_t m_series_stc_readl(struct comedi_device *dev, int reg)
+{
+ unsigned offset;
+
+ switch (reg) {
+ case G_HW_Save_Register(0):
+ offset = M_Offset_G0_HW_Save;
+ break;
+ case G_HW_Save_Register(1):
+ offset = M_Offset_G1_HW_Save;
+ break;
+ case G_Save_Register(0):
+ offset = M_Offset_G0_Save;
+ break;
+ case G_Save_Register(1):
+ offset = M_Offset_G1_Save;
+ break;
+ default:
+ dev_warn(dev->class_dev,
+ "%s: bug! unhandled register=0x%x in switch\n",
+ __func__, reg);
+ return 0;
+ }
+ return ni_readl(dev, offset);
+}
+
+static uint16_t m_series_stc_readw(struct comedi_device *dev, int reg)
+{
+ unsigned offset;
+
+ switch (reg) {
+ case AI_Status_1_Register:
+ offset = M_Offset_AI_Status_1;
+ break;
+ case AO_Status_1_Register:
+ offset = M_Offset_AO_Status_1;
+ break;
+ case AO_Status_2_Register:
+ offset = M_Offset_AO_Status_2;
+ break;
+ case DIO_Serial_Input_Register:
+ return ni_readb(dev, M_Offset_SCXI_Serial_Data_In);
+ case Joint_Status_1_Register:
+ offset = M_Offset_Joint_Status_1;
+ break;
+ case Joint_Status_2_Register:
+ offset = M_Offset_Joint_Status_2;
+ break;
+ case G_Status_Register:
+ offset = M_Offset_G01_Status;
+ break;
+ default:
+ dev_warn(dev->class_dev,
+ "%s: bug! unhandled register=0x%x in switch\n",
+ __func__, reg);
+ return 0;
+ }
+ return ni_readw(dev, offset);
+}
+
+static void ni_stc_writew(struct comedi_device *dev, uint16_t data, int reg)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ if (devpriv->is_m_series) {
+ m_series_stc_writew(dev, data, reg);
+ } else {
+ spin_lock_irqsave(&devpriv->window_lock, flags);
+ if (!devpriv->mite && reg < 8) {
+ ni_writew(dev, data, reg * 2);
+ } else {
+ ni_writew(dev, reg, Window_Address);
+ ni_writew(dev, data, Window_Data);
+ }
+ spin_unlock_irqrestore(&devpriv->window_lock, flags);
+ }
+}
+
+static void ni_stc_writel(struct comedi_device *dev, uint32_t data, int reg)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (devpriv->is_m_series) {
+ m_series_stc_writel(dev, data, reg);
+ } else {
+ ni_stc_writew(dev, data >> 16, reg);
+ ni_stc_writew(dev, data & 0xffff, reg + 1);
+ }
+}
+
+static uint16_t ni_stc_readw(struct comedi_device *dev, int reg)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+ uint16_t val;
+
+ if (devpriv->is_m_series) {
+ val = m_series_stc_readw(dev, reg);
+ } else {
+ spin_lock_irqsave(&devpriv->window_lock, flags);
+ if (!devpriv->mite && reg < 8) {
+ val = ni_readw(dev, reg * 2);
+ } else {
+ ni_writew(dev, reg, Window_Address);
+ val = ni_readw(dev, Window_Data);
+ }
+ spin_unlock_irqrestore(&devpriv->window_lock, flags);
+ }
+ return val;
+}
+
+static uint32_t ni_stc_readl(struct comedi_device *dev, int reg)
+{
+ struct ni_private *devpriv = dev->private;
+ uint32_t val;
+
+ if (devpriv->is_m_series) {
+ val = m_series_stc_readl(dev, reg);
+ } else {
+ val = ni_stc_readw(dev, reg) << 16;
+ val |= ni_stc_readw(dev, reg + 1);
+ }
+ return val;
+}
+
+static inline void ni_set_bitfield(struct comedi_device *dev, int reg,
+ unsigned bit_mask, unsigned bit_values)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags);
+ switch (reg) {
+ case Interrupt_A_Enable_Register:
+ devpriv->int_a_enable_reg &= ~bit_mask;
+ devpriv->int_a_enable_reg |= bit_values & bit_mask;
+ ni_stc_writew(dev, devpriv->int_a_enable_reg,
+ Interrupt_A_Enable_Register);
+ break;
+ case Interrupt_B_Enable_Register:
+ devpriv->int_b_enable_reg &= ~bit_mask;
+ devpriv->int_b_enable_reg |= bit_values & bit_mask;
+ ni_stc_writew(dev, devpriv->int_b_enable_reg,
+ Interrupt_B_Enable_Register);
+ break;
+ case IO_Bidirection_Pin_Register:
+ devpriv->io_bidirection_pin_reg &= ~bit_mask;
+ devpriv->io_bidirection_pin_reg |= bit_values & bit_mask;
+ ni_stc_writew(dev, devpriv->io_bidirection_pin_reg,
+ IO_Bidirection_Pin_Register);
+ break;
+ case AI_AO_Select:
+ devpriv->ai_ao_select_reg &= ~bit_mask;
+ devpriv->ai_ao_select_reg |= bit_values & bit_mask;
+ ni_writeb(dev, devpriv->ai_ao_select_reg, AI_AO_Select);
+ break;
+ case G0_G1_Select:
+ devpriv->g0_g1_select_reg &= ~bit_mask;
+ devpriv->g0_g1_select_reg |= bit_values & bit_mask;
+ ni_writeb(dev, devpriv->g0_g1_select_reg, G0_G1_Select);
+ break;
+ default:
+ dev_err(dev->class_dev, "called with invalid register %d\n",
+ reg);
+ break;
+ }
+ mmiowb();
+ spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags);
+}
+
+#ifdef PCIDMA
+/* DMA channel setup */
+
+/* negative channel means no channel */
+static inline void ni_set_ai_dma_channel(struct comedi_device *dev, int channel)
+{
+ unsigned bitfield;
+
+ if (channel >= 0)
+ bitfield =
+ (ni_stc_dma_channel_select_bitfield(channel) <<
+ AI_DMA_Select_Shift) & AI_DMA_Select_Mask;
+ else
+ bitfield = 0;
+ ni_set_bitfield(dev, AI_AO_Select, AI_DMA_Select_Mask, bitfield);
+}
+
+/* negative channel means no channel */
+static inline void ni_set_ao_dma_channel(struct comedi_device *dev, int channel)
+{
+ unsigned bitfield;
+
+ if (channel >= 0)
+ bitfield =
+ (ni_stc_dma_channel_select_bitfield(channel) <<
+ AO_DMA_Select_Shift) & AO_DMA_Select_Mask;
+ else
+ bitfield = 0;
+ ni_set_bitfield(dev, AI_AO_Select, AO_DMA_Select_Mask, bitfield);
+}
+
+/* negative mite_channel means no channel */
+static inline void ni_set_gpct_dma_channel(struct comedi_device *dev,
+ unsigned gpct_index,
+ int mite_channel)
+{
+ unsigned bitfield;
+
+ if (mite_channel >= 0)
+ bitfield = GPCT_DMA_Select_Bits(gpct_index, mite_channel);
+ else
+ bitfield = 0;
+ ni_set_bitfield(dev, G0_G1_Select, GPCT_DMA_Select_Mask(gpct_index),
+ bitfield);
+}
+
+/* negative mite_channel means no channel */
+static inline void ni_set_cdo_dma_channel(struct comedi_device *dev,
+ int mite_channel)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags);
+ devpriv->cdio_dma_select_reg &= ~CDO_DMA_Select_Mask;
+ if (mite_channel >= 0) {
+ /*XXX just guessing ni_stc_dma_channel_select_bitfield() returns the right bits,
+ under the assumption the cdio dma selection works just like ai/ao/gpct.
+ Definitely works for dma channels 0 and 1. */
+ devpriv->cdio_dma_select_reg |=
+ (ni_stc_dma_channel_select_bitfield(mite_channel) <<
+ CDO_DMA_Select_Shift) & CDO_DMA_Select_Mask;
+ }
+ ni_writeb(dev, devpriv->cdio_dma_select_reg, M_Offset_CDIO_DMA_Select);
+ mmiowb();
+ spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags);
+}
+
+static int ni_request_ai_mite_channel(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ BUG_ON(devpriv->ai_mite_chan);
+ devpriv->ai_mite_chan =
+ mite_request_channel(devpriv->mite, devpriv->ai_mite_ring);
+ if (!devpriv->ai_mite_chan) {
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ dev_err(dev->class_dev,
+ "failed to reserve mite dma channel for analog input\n");
+ return -EBUSY;
+ }
+ devpriv->ai_mite_chan->dir = COMEDI_INPUT;
+ ni_set_ai_dma_channel(dev, devpriv->ai_mite_chan->channel);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ return 0;
+}
+
+static int ni_request_ao_mite_channel(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ BUG_ON(devpriv->ao_mite_chan);
+ devpriv->ao_mite_chan =
+ mite_request_channel(devpriv->mite, devpriv->ao_mite_ring);
+ if (!devpriv->ao_mite_chan) {
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ dev_err(dev->class_dev,
+ "failed to reserve mite dma channel for analog outut\n");
+ return -EBUSY;
+ }
+ devpriv->ao_mite_chan->dir = COMEDI_OUTPUT;
+ ni_set_ao_dma_channel(dev, devpriv->ao_mite_chan->channel);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ return 0;
+}
+
+static int ni_request_gpct_mite_channel(struct comedi_device *dev,
+ unsigned gpct_index,
+ enum comedi_io_direction direction)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+ struct mite_channel *mite_chan;
+
+ BUG_ON(gpct_index >= NUM_GPCT);
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ BUG_ON(devpriv->counter_dev->counters[gpct_index].mite_chan);
+ mite_chan =
+ mite_request_channel(devpriv->mite,
+ devpriv->gpct_mite_ring[gpct_index]);
+ if (!mite_chan) {
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ dev_err(dev->class_dev,
+ "failed to reserve mite dma channel for counter\n");
+ return -EBUSY;
+ }
+ mite_chan->dir = direction;
+ ni_tio_set_mite_channel(&devpriv->counter_dev->counters[gpct_index],
+ mite_chan);
+ ni_set_gpct_dma_channel(dev, gpct_index, mite_chan->channel);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ return 0;
+}
+
+#endif /* PCIDMA */
+
+static int ni_request_cdo_mite_channel(struct comedi_device *dev)
+{
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ BUG_ON(devpriv->cdo_mite_chan);
+ devpriv->cdo_mite_chan =
+ mite_request_channel(devpriv->mite, devpriv->cdo_mite_ring);
+ if (!devpriv->cdo_mite_chan) {
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ dev_err(dev->class_dev,
+ "failed to reserve mite dma channel for correlated digital output\n");
+ return -EBUSY;
+ }
+ devpriv->cdo_mite_chan->dir = COMEDI_OUTPUT;
+ ni_set_cdo_dma_channel(dev, devpriv->cdo_mite_chan->channel);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+#endif /* PCIDMA */
+ return 0;
+}
+
+static void ni_release_ai_mite_channel(struct comedi_device *dev)
+{
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->ai_mite_chan) {
+ ni_set_ai_dma_channel(dev, -1);
+ mite_release_channel(devpriv->ai_mite_chan);
+ devpriv->ai_mite_chan = NULL;
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+#endif /* PCIDMA */
+}
+
+static void ni_release_ao_mite_channel(struct comedi_device *dev)
+{
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->ao_mite_chan) {
+ ni_set_ao_dma_channel(dev, -1);
+ mite_release_channel(devpriv->ao_mite_chan);
+ devpriv->ao_mite_chan = NULL;
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+#endif /* PCIDMA */
+}
+
+#ifdef PCIDMA
+static void ni_release_gpct_mite_channel(struct comedi_device *dev,
+ unsigned gpct_index)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ BUG_ON(gpct_index >= NUM_GPCT);
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->counter_dev->counters[gpct_index].mite_chan) {
+ struct mite_channel *mite_chan =
+ devpriv->counter_dev->counters[gpct_index].mite_chan;
+
+ ni_set_gpct_dma_channel(dev, gpct_index, -1);
+ ni_tio_set_mite_channel(&devpriv->
+ counter_dev->counters[gpct_index],
+ NULL);
+ mite_release_channel(mite_chan);
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+}
+#endif /* PCIDMA */
+
+static void ni_release_cdo_mite_channel(struct comedi_device *dev)
+{
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->cdo_mite_chan) {
+ ni_set_cdo_dma_channel(dev, -1);
+ mite_release_channel(devpriv->cdo_mite_chan);
+ devpriv->cdo_mite_chan = NULL;
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+#endif /* PCIDMA */
+}
+
+#ifdef PCIDMA
+static void ni_e_series_enable_second_irq(struct comedi_device *dev,
+ unsigned gpct_index, short enable)
+{
+ struct ni_private *devpriv = dev->private;
+ uint16_t val = 0;
+ int reg;
+
+ if (devpriv->is_m_series || gpct_index > 1)
+ return;
+
+ /*
+ * e-series boards use the second irq signals to generate
+ * dma requests for their counters
+ */
+ if (gpct_index == 0) {
+ reg = Second_IRQ_A_Enable_Register;
+ if (enable)
+ val = G0_Gate_Second_Irq_Enable;
+ } else {
+ reg = Second_IRQ_B_Enable_Register;
+ if (enable)
+ val = G1_Gate_Second_Irq_Enable;
+ }
+ ni_stc_writew(dev, val, reg);
+}
+#endif /* PCIDMA */
+
+static void ni_clear_ai_fifo(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ static const int timeout = 10000;
+ int i;
+
+ if (devpriv->is_6143) {
+ /* Flush the 6143 data FIFO */
+ ni_writel(dev, 0x10, AIFIFO_Control_6143);
+ ni_writel(dev, 0x00, AIFIFO_Control_6143);
+ /* Wait for complete */
+ for (i = 0; i < timeout; i++) {
+ if (!(ni_readl(dev, AIFIFO_Status_6143) & 0x10))
+ break;
+ udelay(1);
+ }
+ if (i == timeout)
+ dev_err(dev->class_dev, "FIFO flush timeout\n");
+ } else {
+ ni_stc_writew(dev, 1, ADC_FIFO_Clear);
+ if (devpriv->is_625x) {
+ ni_writeb(dev, 0, M_Offset_Static_AI_Control(0));
+ ni_writeb(dev, 1, M_Offset_Static_AI_Control(0));
+#if 0
+ /* the NI example code does 3 convert pulses for 625x boards,
+ but that appears to be wrong in practice. */
+ ni_stc_writew(dev, AI_CONVERT_Pulse,
+ AI_Command_1_Register);
+ ni_stc_writew(dev, AI_CONVERT_Pulse,
+ AI_Command_1_Register);
+ ni_stc_writew(dev, AI_CONVERT_Pulse,
+ AI_Command_1_Register);
+#endif
+ }
+ }
+}
+
+static inline void ni_ao_win_outw(struct comedi_device *dev, uint16_t data,
+ int addr)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->window_lock, flags);
+ ni_writew(dev, addr, AO_Window_Address_611x);
+ ni_writew(dev, data, AO_Window_Data_611x);
+ spin_unlock_irqrestore(&devpriv->window_lock, flags);
+}
+
+static inline void ni_ao_win_outl(struct comedi_device *dev, uint32_t data,
+ int addr)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->window_lock, flags);
+ ni_writew(dev, addr, AO_Window_Address_611x);
+ ni_writel(dev, data, AO_Window_Data_611x);
+ spin_unlock_irqrestore(&devpriv->window_lock, flags);
+}
+
+static inline unsigned short ni_ao_win_inw(struct comedi_device *dev, int addr)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+ unsigned short data;
+
+ spin_lock_irqsave(&devpriv->window_lock, flags);
+ ni_writew(dev, addr, AO_Window_Address_611x);
+ data = ni_readw(dev, AO_Window_Data_611x);
+ spin_unlock_irqrestore(&devpriv->window_lock, flags);
+ return data;
+}
+
+/* ni_set_bits( ) allows different parts of the ni_mio_common driver to
+* share registers (such as Interrupt_A_Register) without interfering with
+* each other.
+*
+* NOTE: the switch/case statements are optimized out for a constant argument
+* so this is actually quite fast--- If you must wrap another function around this
+* make it inline to avoid a large speed penalty.
+*
+* value should only be 1 or 0.
+*/
+static inline void ni_set_bits(struct comedi_device *dev, int reg,
+ unsigned bits, unsigned value)
+{
+ unsigned bit_values;
+
+ if (value)
+ bit_values = bits;
+ else
+ bit_values = 0;
+ ni_set_bitfield(dev, reg, bits, bit_values);
+}
+
+#ifdef PCIDMA
+static void ni_sync_ai_dma(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->ai_mite_chan)
+ mite_sync_input_dma(devpriv->ai_mite_chan, s);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+}
+
+static int ni_ai_drain_dma(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ int i;
+ static const int timeout = 10000;
+ unsigned long flags;
+ int retval = 0;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->ai_mite_chan) {
+ for (i = 0; i < timeout; i++) {
+ if ((ni_stc_readw(dev, AI_Status_1_Register) &
+ AI_FIFO_Empty_St)
+ && mite_bytes_in_transit(devpriv->ai_mite_chan) ==
+ 0)
+ break;
+ udelay(5);
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev, "timed out\n");
+ dev_err(dev->class_dev,
+ "mite_bytes_in_transit=%i, AI_Status1_Register=0x%x\n",
+ mite_bytes_in_transit(devpriv->ai_mite_chan),
+ ni_stc_readw(dev, AI_Status_1_Register));
+ retval = -1;
+ }
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+
+ ni_sync_ai_dma(dev);
+
+ return retval;
+}
+
+static void mite_handle_b_linkc(struct mite_struct *mite,
+ struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->ao_mite_chan)
+ mite_sync_output_dma(devpriv->ao_mite_chan, s);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+}
+
+static int ni_ao_wait_for_dma_load(struct comedi_device *dev)
+{
+ static const int timeout = 10000;
+ int i;
+
+ for (i = 0; i < timeout; i++) {
+ unsigned short b_status;
+
+ b_status = ni_stc_readw(dev, AO_Status_1_Register);
+ if (b_status & AO_FIFO_Half_Full_St)
+ break;
+ /* if we poll too often, the pci bus activity seems
+ to slow the dma transfer down */
+ udelay(10);
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev, "timed out waiting for dma load\n");
+ return -EPIPE;
+ }
+ return 0;
+}
+#endif /* PCIDMA */
+
+#ifndef PCIDMA
+
+static void ni_ao_fifo_load(struct comedi_device *dev,
+ struct comedi_subdevice *s, int n)
+{
+ struct ni_private *devpriv = dev->private;
+ int i;
+ unsigned short d;
+ u32 packed_data;
+
+ for (i = 0; i < n; i++) {
+ comedi_buf_read_samples(s, &d, 1);
+
+ if (devpriv->is_6xxx) {
+ packed_data = d & 0xffff;
+ /* 6711 only has 16 bit wide ao fifo */
+ if (!devpriv->is_6711) {
+ comedi_buf_read_samples(s, &d, 1);
+ i++;
+ packed_data |= (d << 16) & 0xffff0000;
+ }
+ ni_writel(dev, packed_data, DAC_FIFO_Data_611x);
+ } else {
+ ni_writew(dev, d, DAC_FIFO_Data);
+ }
+ }
+}
+
+/*
+ * There's a small problem if the FIFO gets really low and we
+ * don't have the data to fill it. Basically, if after we fill
+ * the FIFO with all the data available, the FIFO is _still_
+ * less than half full, we never clear the interrupt. If the
+ * IRQ is in edge mode, we never get another interrupt, because
+ * this one wasn't cleared. If in level mode, we get flooded
+ * with interrupts that we can't fulfill, because nothing ever
+ * gets put into the buffer.
+ *
+ * This kind of situation is recoverable, but it is easier to
+ * just pretend we had a FIFO underrun, since there is a good
+ * chance it will happen anyway. This is _not_ the case for
+ * RT code, as RT code might purposely be running close to the
+ * metal. Needs to be fixed eventually.
+ */
+static int ni_ao_fifo_half_empty(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ unsigned int nbytes;
+ unsigned int nsamples;
+
+ nbytes = comedi_buf_read_n_available(s);
+ if (nbytes == 0) {
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ return 0;
+ }
+
+ nsamples = comedi_bytes_to_samples(s, nbytes);
+ if (nsamples > board->ao_fifo_depth / 2)
+ nsamples = board->ao_fifo_depth / 2;
+
+ ni_ao_fifo_load(dev, s, nsamples);
+
+ return 1;
+}
+
+static int ni_ao_prep_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ unsigned int nbytes;
+ unsigned int nsamples;
+
+ /* reset fifo */
+ ni_stc_writew(dev, 1, DAC_FIFO_Clear);
+ if (devpriv->is_6xxx)
+ ni_ao_win_outl(dev, 0x6, AO_FIFO_Offset_Load_611x);
+
+ /* load some data */
+ nbytes = comedi_buf_read_n_available(s);
+ if (nbytes == 0)
+ return 0;
+
+ nsamples = comedi_bytes_to_samples(s, nbytes);
+ if (nsamples > board->ao_fifo_depth)
+ nsamples = board->ao_fifo_depth;
+
+ ni_ao_fifo_load(dev, s, nsamples);
+
+ return nsamples;
+}
+
+static void ni_ai_fifo_read(struct comedi_device *dev,
+ struct comedi_subdevice *s, int n)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ u32 dl;
+ unsigned short data;
+ int i;
+
+ if (devpriv->is_611x) {
+ for (i = 0; i < n / 2; i++) {
+ dl = ni_readl(dev, ADC_FIFO_Data_611x);
+ /* This may get the hi/lo data in the wrong order */
+ data = (dl >> 16) & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ data = dl & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+ /* Check if there's a single sample stuck in the FIFO */
+ if (n % 2) {
+ dl = ni_readl(dev, ADC_FIFO_Data_611x);
+ data = dl & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+ } else if (devpriv->is_6143) {
+ /* This just reads the FIFO assuming the data is present, no checks on the FIFO status are performed */
+ for (i = 0; i < n / 2; i++) {
+ dl = ni_readl(dev, AIFIFO_Data_6143);
+
+ data = (dl >> 16) & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ data = dl & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+ if (n % 2) {
+ /* Assume there is a single sample stuck in the FIFO */
+ /* Get stranded sample into FIFO */
+ ni_writel(dev, 0x01, AIFIFO_Control_6143);
+ dl = ni_readl(dev, AIFIFO_Data_6143);
+ data = (dl >> 16) & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+ } else {
+ if (n > sizeof(devpriv->ai_fifo_buffer) /
+ sizeof(devpriv->ai_fifo_buffer[0])) {
+ dev_err(dev->class_dev,
+ "bug! ai_fifo_buffer too small\n");
+ async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+ for (i = 0; i < n; i++) {
+ devpriv->ai_fifo_buffer[i] =
+ ni_readw(dev, ADC_FIFO_Data_Register);
+ }
+ comedi_buf_write_samples(s, devpriv->ai_fifo_buffer, n);
+ }
+}
+
+static void ni_handle_fifo_half_full(struct comedi_device *dev)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int n;
+
+ n = board->ai_fifo_depth / 2;
+
+ ni_ai_fifo_read(dev, s, n);
+}
+#endif
+
+/*
+ Empties the AI fifo
+*/
+static void ni_handle_fifo_dregs(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ u32 dl;
+ unsigned short data;
+ unsigned short fifo_empty;
+ int i;
+
+ if (devpriv->is_611x) {
+ while ((ni_stc_readw(dev, AI_Status_1_Register) &
+ AI_FIFO_Empty_St) == 0) {
+ dl = ni_readl(dev, ADC_FIFO_Data_611x);
+
+ /* This may get the hi/lo data in the wrong order */
+ data = dl >> 16;
+ comedi_buf_write_samples(s, &data, 1);
+ data = dl & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+ } else if (devpriv->is_6143) {
+ i = 0;
+ while (ni_readl(dev, AIFIFO_Status_6143) & 0x04) {
+ dl = ni_readl(dev, AIFIFO_Data_6143);
+
+ /* This may get the hi/lo data in the wrong order */
+ data = dl >> 16;
+ comedi_buf_write_samples(s, &data, 1);
+ data = dl & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ i += 2;
+ }
+ /* Check if stranded sample is present */
+ if (ni_readl(dev, AIFIFO_Status_6143) & 0x01) {
+ /* Get stranded sample into FIFO */
+ ni_writel(dev, 0x01, AIFIFO_Control_6143);
+ dl = ni_readl(dev, AIFIFO_Data_6143);
+ data = (dl >> 16) & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+
+ } else {
+ fifo_empty = ni_stc_readw(dev, AI_Status_1_Register) &
+ AI_FIFO_Empty_St;
+ while (fifo_empty == 0) {
+ for (i = 0;
+ i <
+ sizeof(devpriv->ai_fifo_buffer) /
+ sizeof(devpriv->ai_fifo_buffer[0]); i++) {
+ fifo_empty = ni_stc_readw(dev,
+ AI_Status_1_Register) &
+ AI_FIFO_Empty_St;
+ if (fifo_empty)
+ break;
+ devpriv->ai_fifo_buffer[i] =
+ ni_readw(dev, ADC_FIFO_Data_Register);
+ }
+ comedi_buf_write_samples(s, devpriv->ai_fifo_buffer, i);
+ }
+ }
+}
+
+static void get_last_sample_611x(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned short data;
+ u32 dl;
+
+ if (!devpriv->is_611x)
+ return;
+
+ /* Check if there's a single sample stuck in the FIFO */
+ if (ni_readb(dev, XXX_Status) & 0x80) {
+ dl = ni_readl(dev, ADC_FIFO_Data_611x);
+ data = dl & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+}
+
+static void get_last_sample_6143(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned short data;
+ u32 dl;
+
+ if (!devpriv->is_6143)
+ return;
+
+ /* Check if there's a single sample stuck in the FIFO */
+ if (ni_readl(dev, AIFIFO_Status_6143) & 0x01) {
+ /* Get stranded sample into FIFO */
+ ni_writel(dev, 0x01, AIFIFO_Control_6143);
+ dl = ni_readl(dev, AIFIFO_Data_6143);
+
+ /* This may get the hi/lo data in the wrong order */
+ data = (dl >> 16) & 0xffff;
+ comedi_buf_write_samples(s, &data, 1);
+ }
+}
+
+static void shutdown_ai_command(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s = dev->read_subdev;
+
+#ifdef PCIDMA
+ ni_ai_drain_dma(dev);
+#endif
+ ni_handle_fifo_dregs(dev);
+ get_last_sample_611x(dev);
+ get_last_sample_6143(dev);
+
+ s->async->events |= COMEDI_CB_EOA;
+}
+
+static void ni_handle_eos(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (devpriv->aimode == AIMODE_SCAN) {
+#ifdef PCIDMA
+ static const int timeout = 10;
+ int i;
+
+ for (i = 0; i < timeout; i++) {
+ ni_sync_ai_dma(dev);
+ if ((s->async->events & COMEDI_CB_EOS))
+ break;
+ udelay(1);
+ }
+#else
+ ni_handle_fifo_dregs(dev);
+ s->async->events |= COMEDI_CB_EOS;
+#endif
+ }
+ /* handle special case of single scan using AI_End_On_End_Of_Scan */
+ if ((devpriv->ai_cmd2 & AI_End_On_End_Of_Scan))
+ shutdown_ai_command(dev);
+}
+
+static void handle_gpct_interrupt(struct comedi_device *dev,
+ unsigned short counter_index)
+{
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+
+ s = &dev->subdevices[NI_GPCT_SUBDEV(counter_index)];
+
+ ni_tio_handle_interrupt(&devpriv->counter_dev->counters[counter_index],
+ s);
+ comedi_handle_events(dev, s);
+#endif
+}
+
+static void ack_a_interrupt(struct comedi_device *dev, unsigned short a_status)
+{
+ unsigned short ack = 0;
+
+ if (a_status & AI_SC_TC_St)
+ ack |= AI_SC_TC_Interrupt_Ack;
+ if (a_status & AI_START1_St)
+ ack |= AI_START1_Interrupt_Ack;
+ if (a_status & AI_START_St)
+ ack |= AI_START_Interrupt_Ack;
+ if (a_status & AI_STOP_St)
+ /* not sure why we used to ack the START here also, instead of doing it independently. Frank Hess 2007-07-06 */
+ ack |= AI_STOP_Interrupt_Ack /*| AI_START_Interrupt_Ack */;
+ if (ack)
+ ni_stc_writew(dev, ack, Interrupt_A_Ack_Register);
+}
+
+static void handle_a_interrupt(struct comedi_device *dev, unsigned short status,
+ unsigned ai_mite_status)
+{
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ /* 67xx boards don't have ai subdevice, but their gpct0 might generate an a interrupt */
+ if (s->type == COMEDI_SUBD_UNUSED)
+ return;
+
+#ifdef PCIDMA
+ if (ai_mite_status & CHSR_LINKC)
+ ni_sync_ai_dma(dev);
+
+ if (ai_mite_status & ~(CHSR_INT | CHSR_LINKC | CHSR_DONE | CHSR_MRDY |
+ CHSR_DRDY | CHSR_DRQ1 | CHSR_DRQ0 | CHSR_ERROR |
+ CHSR_SABORT | CHSR_XFERR | CHSR_LxERR_mask)) {
+ dev_err(dev->class_dev,
+ "unknown mite interrupt (ai_mite_status=%08x)\n",
+ ai_mite_status);
+ s->async->events |= COMEDI_CB_ERROR;
+ /* disable_irq(dev->irq); */
+ }
+#endif
+
+ /* test for all uncommon interrupt events at the same time */
+ if (status & (AI_Overrun_St | AI_Overflow_St | AI_SC_TC_Error_St |
+ AI_SC_TC_St | AI_START1_St)) {
+ if (status == 0xffff) {
+ dev_err(dev->class_dev, "Card removed?\n");
+ /* we probably aren't even running a command now,
+ * so it's a good idea to be careful. */
+ if (comedi_is_subdevice_running(s)) {
+ s->async->events |= COMEDI_CB_ERROR;
+ comedi_handle_events(dev, s);
+ }
+ return;
+ }
+ if (status & (AI_Overrun_St | AI_Overflow_St |
+ AI_SC_TC_Error_St)) {
+ dev_err(dev->class_dev, "ai error a_status=%04x\n",
+ status);
+
+ shutdown_ai_command(dev);
+
+ s->async->events |= COMEDI_CB_ERROR;
+ if (status & (AI_Overrun_St | AI_Overflow_St))
+ s->async->events |= COMEDI_CB_OVERFLOW;
+
+ comedi_handle_events(dev, s);
+ return;
+ }
+ if (status & AI_SC_TC_St) {
+ if (cmd->stop_src == TRIG_COUNT)
+ shutdown_ai_command(dev);
+ }
+ }
+#ifndef PCIDMA
+ if (status & AI_FIFO_Half_Full_St) {
+ int i;
+ static const int timeout = 10;
+ /* pcmcia cards (at least 6036) seem to stop producing interrupts if we
+ *fail to get the fifo less than half full, so loop to be sure.*/
+ for (i = 0; i < timeout; ++i) {
+ ni_handle_fifo_half_full(dev);
+ if ((ni_stc_readw(dev, AI_Status_1_Register) &
+ AI_FIFO_Half_Full_St) == 0)
+ break;
+ }
+ }
+#endif /* !PCIDMA */
+
+ if ((status & AI_STOP_St))
+ ni_handle_eos(dev, s);
+
+ comedi_handle_events(dev, s);
+}
+
+static void ack_b_interrupt(struct comedi_device *dev, unsigned short b_status)
+{
+ unsigned short ack = 0;
+
+ if (b_status & AO_BC_TC_St)
+ ack |= AO_BC_TC_Interrupt_Ack;
+ if (b_status & AO_Overrun_St)
+ ack |= AO_Error_Interrupt_Ack;
+ if (b_status & AO_START_St)
+ ack |= AO_START_Interrupt_Ack;
+ if (b_status & AO_START1_St)
+ ack |= AO_START1_Interrupt_Ack;
+ if (b_status & AO_UC_TC_St)
+ ack |= AO_UC_TC_Interrupt_Ack;
+ if (b_status & AO_UI2_TC_St)
+ ack |= AO_UI2_TC_Interrupt_Ack;
+ if (b_status & AO_UPDATE_St)
+ ack |= AO_UPDATE_Interrupt_Ack;
+ if (ack)
+ ni_stc_writew(dev, ack, Interrupt_B_Ack_Register);
+}
+
+static void handle_b_interrupt(struct comedi_device *dev,
+ unsigned short b_status, unsigned ao_mite_status)
+{
+ struct comedi_subdevice *s = dev->write_subdev;
+ /* unsigned short ack=0; */
+
+#ifdef PCIDMA
+ /* Currently, mite.c requires us to handle LINKC */
+ if (ao_mite_status & CHSR_LINKC) {
+ struct ni_private *devpriv = dev->private;
+
+ mite_handle_b_linkc(devpriv->mite, dev);
+ }
+
+ if (ao_mite_status & ~(CHSR_INT | CHSR_LINKC | CHSR_DONE | CHSR_MRDY |
+ CHSR_DRDY | CHSR_DRQ1 | CHSR_DRQ0 | CHSR_ERROR |
+ CHSR_SABORT | CHSR_XFERR | CHSR_LxERR_mask)) {
+ dev_err(dev->class_dev,
+ "unknown mite interrupt (ao_mite_status=%08x)\n",
+ ao_mite_status);
+ s->async->events |= COMEDI_CB_ERROR;
+ }
+#endif
+
+ if (b_status == 0xffff)
+ return;
+ if (b_status & AO_Overrun_St) {
+ dev_err(dev->class_dev,
+ "AO FIFO underrun status=0x%04x status2=0x%04x\n",
+ b_status, ni_stc_readw(dev, AO_Status_2_Register));
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+
+ if (b_status & AO_BC_TC_St)
+ s->async->events |= COMEDI_CB_EOA;
+
+#ifndef PCIDMA
+ if (b_status & AO_FIFO_Request_St) {
+ int ret;
+
+ ret = ni_ao_fifo_half_empty(dev, s);
+ if (!ret) {
+ dev_err(dev->class_dev, "AO buffer underrun\n");
+ ni_set_bits(dev, Interrupt_B_Enable_Register,
+ AO_FIFO_Interrupt_Enable |
+ AO_Error_Interrupt_Enable, 0);
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+ }
+#endif
+
+ comedi_handle_events(dev, s);
+}
+
+static void ni_ai_munge(struct comedi_device *dev, struct comedi_subdevice *s,
+ void *data, unsigned int num_bytes,
+ unsigned int chan_index)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int nsamples = comedi_bytes_to_samples(s, num_bytes);
+ unsigned short *array = data;
+ unsigned int *larray = data;
+ unsigned int i;
+
+ for (i = 0; i < nsamples; i++) {
+#ifdef PCIDMA
+ if (s->subdev_flags & SDF_LSAMPL)
+ larray[i] = le32_to_cpu(larray[i]);
+ else
+ array[i] = le16_to_cpu(array[i]);
+#endif
+ if (s->subdev_flags & SDF_LSAMPL)
+ larray[i] += devpriv->ai_offset[chan_index];
+ else
+ array[i] += devpriv->ai_offset[chan_index];
+ chan_index++;
+ chan_index %= cmd->chanlist_len;
+ }
+}
+
+#ifdef PCIDMA
+
+static int ni_ai_setup_MITE_dma(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int retval;
+ unsigned long flags;
+
+ retval = ni_request_ai_mite_channel(dev);
+ if (retval)
+ return retval;
+
+ /* write alloc the entire buffer */
+ comedi_buf_write_alloc(s, s->async->prealloc_bufsz);
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (!devpriv->ai_mite_chan) {
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ return -EIO;
+ }
+
+ if (devpriv->is_611x || devpriv->is_6143)
+ mite_prep_dma(devpriv->ai_mite_chan, 32, 16);
+ else if (devpriv->is_628x)
+ mite_prep_dma(devpriv->ai_mite_chan, 32, 32);
+ else
+ mite_prep_dma(devpriv->ai_mite_chan, 16, 16);
+
+ /*start the MITE */
+ mite_dma_arm(devpriv->ai_mite_chan);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+
+ return 0;
+}
+
+static int ni_ao_setup_MITE_dma(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ int retval;
+ unsigned long flags;
+
+ retval = ni_request_ao_mite_channel(dev);
+ if (retval)
+ return retval;
+
+ /* read alloc the entire buffer */
+ comedi_buf_read_alloc(s, s->async->prealloc_bufsz);
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->ao_mite_chan) {
+ if (devpriv->is_611x || devpriv->is_6713) {
+ mite_prep_dma(devpriv->ao_mite_chan, 32, 32);
+ } else {
+ /* doing 32 instead of 16 bit wide transfers from memory
+ makes the mite do 32 bit pci transfers, doubling pci bandwidth. */
+ mite_prep_dma(devpriv->ao_mite_chan, 16, 32);
+ }
+ mite_dma_arm(devpriv->ao_mite_chan);
+ } else {
+ retval = -EIO;
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+
+ return retval;
+}
+
+#endif /* PCIDMA */
+
+/*
+ used for both cancel ioctl and board initialization
+
+ this is pretty harsh for a cancel, but it works...
+ */
+
+static int ni_ai_reset(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+
+ ni_release_ai_mite_channel(dev);
+ /* ai configuration */
+ ni_stc_writew(dev, AI_Configuration_Start | AI_Reset,
+ Joint_Reset_Register);
+
+ ni_set_bits(dev, Interrupt_A_Enable_Register,
+ AI_SC_TC_Interrupt_Enable | AI_START1_Interrupt_Enable |
+ AI_START2_Interrupt_Enable | AI_START_Interrupt_Enable |
+ AI_STOP_Interrupt_Enable | AI_Error_Interrupt_Enable |
+ AI_FIFO_Interrupt_Enable, 0);
+
+ ni_clear_ai_fifo(dev);
+
+ if (!devpriv->is_6143)
+ ni_writeb(dev, 0, Misc_Command);
+
+ ni_stc_writew(dev, AI_Disarm, AI_Command_1_Register); /* reset pulses */
+ ni_stc_writew(dev, AI_Start_Stop | AI_Mode_1_Reserved
+ /*| AI_Trigger_Once */,
+ AI_Mode_1_Register);
+ ni_stc_writew(dev, 0x0000, AI_Mode_2_Register);
+ /* generate FIFO interrupts on non-empty */
+ ni_stc_writew(dev, (0 << 6) | 0x0000, AI_Mode_3_Register);
+ if (devpriv->is_611x) {
+ ni_stc_writew(dev,
+ AI_SHIFTIN_Pulse_Width |
+ AI_SOC_Polarity |
+ AI_LOCALMUX_CLK_Pulse_Width,
+ AI_Personal_Register);
+ ni_stc_writew(dev,
+ AI_SCAN_IN_PROG_Output_Select(3) |
+ AI_EXTMUX_CLK_Output_Select(0) |
+ AI_LOCALMUX_CLK_Output_Select(2) |
+ AI_SC_TC_Output_Select(3) |
+ AI_CONVERT_Output_Select
+ (AI_CONVERT_Output_Enable_High),
+ AI_Output_Control_Register);
+ } else if (devpriv->is_6143) {
+ ni_stc_writew(dev, AI_SHIFTIN_Pulse_Width |
+ AI_SOC_Polarity |
+ AI_LOCALMUX_CLK_Pulse_Width,
+ AI_Personal_Register);
+ ni_stc_writew(dev,
+ AI_SCAN_IN_PROG_Output_Select(3) |
+ AI_EXTMUX_CLK_Output_Select(0) |
+ AI_LOCALMUX_CLK_Output_Select(2) |
+ AI_SC_TC_Output_Select(3) |
+ AI_CONVERT_Output_Select
+ (AI_CONVERT_Output_Enable_Low),
+ AI_Output_Control_Register);
+ } else {
+ unsigned ai_output_control_bits;
+
+ ni_stc_writew(dev,
+ AI_SHIFTIN_Pulse_Width |
+ AI_SOC_Polarity |
+ AI_CONVERT_Pulse_Width |
+ AI_LOCALMUX_CLK_Pulse_Width,
+ AI_Personal_Register);
+ ai_output_control_bits =
+ AI_SCAN_IN_PROG_Output_Select(3) |
+ AI_EXTMUX_CLK_Output_Select(0) |
+ AI_LOCALMUX_CLK_Output_Select(2) |
+ AI_SC_TC_Output_Select(3);
+ if (devpriv->is_622x)
+ ai_output_control_bits |=
+ AI_CONVERT_Output_Select
+ (AI_CONVERT_Output_Enable_High);
+ else
+ ai_output_control_bits |=
+ AI_CONVERT_Output_Select
+ (AI_CONVERT_Output_Enable_Low);
+ ni_stc_writew(dev, ai_output_control_bits,
+ AI_Output_Control_Register);
+ }
+ /* the following registers should not be changed, because there
+ * are no backup registers in devpriv. If you want to change
+ * any of these, add a backup register and other appropriate code:
+ * AI_Mode_1_Register
+ * AI_Mode_3_Register
+ * AI_Personal_Register
+ * AI_Output_Control_Register
+ */
+ ni_stc_writew(dev,
+ AI_SC_TC_Error_Confirm |
+ AI_START_Interrupt_Ack |
+ AI_START2_Interrupt_Ack |
+ AI_START1_Interrupt_Ack |
+ AI_SC_TC_Interrupt_Ack |
+ AI_Error_Interrupt_Ack |
+ AI_STOP_Interrupt_Ack,
+ Interrupt_A_Ack_Register); /* clear interrupts */
+
+ ni_stc_writew(dev, AI_Configuration_End, Joint_Reset_Register);
+
+ return 0;
+}
+
+static int ni_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ unsigned long flags;
+ int count;
+
+ /* lock to avoid race with interrupt handler */
+ spin_lock_irqsave(&dev->spinlock, flags);
+#ifndef PCIDMA
+ ni_handle_fifo_dregs(dev);
+#else
+ ni_sync_ai_dma(dev);
+#endif
+ count = comedi_buf_n_bytes_ready(s);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return count;
+}
+
+static void ni_prime_channelgain_list(struct comedi_device *dev)
+{
+ int i;
+
+ ni_stc_writew(dev, AI_CONVERT_Pulse, AI_Command_1_Register);
+ for (i = 0; i < NI_TIMEOUT; ++i) {
+ if (!(ni_stc_readw(dev, AI_Status_1_Register) &
+ AI_FIFO_Empty_St)) {
+ ni_stc_writew(dev, 1, ADC_FIFO_Clear);
+ return;
+ }
+ udelay(1);
+ }
+ dev_err(dev->class_dev, "timeout loading channel/gain list\n");
+}
+
+static void ni_m_series_load_channelgain_list(struct comedi_device *dev,
+ unsigned int n_chan,
+ unsigned int *list)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ unsigned int chan, range, aref;
+ unsigned int i;
+ unsigned int dither;
+ unsigned range_code;
+
+ ni_stc_writew(dev, 1, Configuration_Memory_Clear);
+
+ if ((list[0] & CR_ALT_SOURCE)) {
+ unsigned bypass_bits;
+
+ chan = CR_CHAN(list[0]);
+ range = CR_RANGE(list[0]);
+ range_code = ni_gainlkup[board->gainlkup][range];
+ dither = (list[0] & CR_ALT_FILTER) != 0;
+ bypass_bits = MSeries_AI_Bypass_Config_FIFO_Bit;
+ bypass_bits |= chan;
+ bypass_bits |=
+ (devpriv->ai_calib_source) &
+ (MSeries_AI_Bypass_Cal_Sel_Pos_Mask |
+ MSeries_AI_Bypass_Cal_Sel_Neg_Mask |
+ MSeries_AI_Bypass_Mode_Mux_Mask |
+ MSeries_AO_Bypass_AO_Cal_Sel_Mask);
+ bypass_bits |= MSeries_AI_Bypass_Gain_Bits(range_code);
+ if (dither)
+ bypass_bits |= MSeries_AI_Bypass_Dither_Bit;
+ /* don't use 2's complement encoding */
+ bypass_bits |= MSeries_AI_Bypass_Polarity_Bit;
+ ni_writel(dev, bypass_bits, M_Offset_AI_Config_FIFO_Bypass);
+ } else {
+ ni_writel(dev, 0, M_Offset_AI_Config_FIFO_Bypass);
+ }
+ for (i = 0; i < n_chan; i++) {
+ unsigned config_bits = 0;
+
+ chan = CR_CHAN(list[i]);
+ aref = CR_AREF(list[i]);
+ range = CR_RANGE(list[i]);
+ dither = (list[i] & CR_ALT_FILTER) != 0;
+
+ range_code = ni_gainlkup[board->gainlkup][range];
+ devpriv->ai_offset[i] = 0;
+ switch (aref) {
+ case AREF_DIFF:
+ config_bits |=
+ MSeries_AI_Config_Channel_Type_Differential_Bits;
+ break;
+ case AREF_COMMON:
+ config_bits |=
+ MSeries_AI_Config_Channel_Type_Common_Ref_Bits;
+ break;
+ case AREF_GROUND:
+ config_bits |=
+ MSeries_AI_Config_Channel_Type_Ground_Ref_Bits;
+ break;
+ case AREF_OTHER:
+ break;
+ }
+ config_bits |= MSeries_AI_Config_Channel_Bits(chan);
+ config_bits |=
+ MSeries_AI_Config_Bank_Bits(board->reg_type, chan);
+ config_bits |= MSeries_AI_Config_Gain_Bits(range_code);
+ if (i == n_chan - 1)
+ config_bits |= MSeries_AI_Config_Last_Channel_Bit;
+ if (dither)
+ config_bits |= MSeries_AI_Config_Dither_Bit;
+ /* don't use 2's complement encoding */
+ config_bits |= MSeries_AI_Config_Polarity_Bit;
+ ni_writew(dev, config_bits, M_Offset_AI_Config_FIFO_Data);
+ }
+ ni_prime_channelgain_list(dev);
+}
+
+/*
+ * Notes on the 6110 and 6111:
+ * These boards a slightly different than the rest of the series, since
+ * they have multiple A/D converters.
+ * From the driver side, the configuration memory is a
+ * little different.
+ * Configuration Memory Low:
+ * bits 15-9: same
+ * bit 8: unipolar/bipolar (should be 0 for bipolar)
+ * bits 0-3: gain. This is 4 bits instead of 3 for the other boards
+ * 1001 gain=0.1 (+/- 50)
+ * 1010 0.2
+ * 1011 0.1
+ * 0001 1
+ * 0010 2
+ * 0011 5
+ * 0100 10
+ * 0101 20
+ * 0110 50
+ * Configuration Memory High:
+ * bits 12-14: Channel Type
+ * 001 for differential
+ * 000 for calibration
+ * bit 11: coupling (this is not currently handled)
+ * 1 AC coupling
+ * 0 DC coupling
+ * bits 0-2: channel
+ * valid channels are 0-3
+ */
+static void ni_load_channelgain_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int n_chan, unsigned int *list)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ unsigned int offset = (s->maxdata + 1) >> 1;
+ unsigned int chan, range, aref;
+ unsigned int i;
+ unsigned int hi, lo;
+ unsigned int dither;
+
+ if (devpriv->is_m_series) {
+ ni_m_series_load_channelgain_list(dev, n_chan, list);
+ return;
+ }
+ if (n_chan == 1 && !devpriv->is_611x && !devpriv->is_6143) {
+ if (devpriv->changain_state
+ && devpriv->changain_spec == list[0]) {
+ /* ready to go. */
+ return;
+ }
+ devpriv->changain_state = 1;
+ devpriv->changain_spec = list[0];
+ } else {
+ devpriv->changain_state = 0;
+ }
+
+ ni_stc_writew(dev, 1, Configuration_Memory_Clear);
+
+ /* Set up Calibration mode if required */
+ if (devpriv->is_6143) {
+ if ((list[0] & CR_ALT_SOURCE)
+ && !devpriv->ai_calib_source_enabled) {
+ /* Strobe Relay enable bit */
+ ni_writew(dev, devpriv->ai_calib_source |
+ Calibration_Channel_6143_RelayOn,
+ Calibration_Channel_6143);
+ ni_writew(dev, devpriv->ai_calib_source,
+ Calibration_Channel_6143);
+ devpriv->ai_calib_source_enabled = 1;
+ msleep_interruptible(100); /* Allow relays to change */
+ } else if (!(list[0] & CR_ALT_SOURCE)
+ && devpriv->ai_calib_source_enabled) {
+ /* Strobe Relay disable bit */
+ ni_writew(dev, devpriv->ai_calib_source |
+ Calibration_Channel_6143_RelayOff,
+ Calibration_Channel_6143);
+ ni_writew(dev, devpriv->ai_calib_source,
+ Calibration_Channel_6143);
+ devpriv->ai_calib_source_enabled = 0;
+ msleep_interruptible(100); /* Allow relays to change */
+ }
+ }
+
+ for (i = 0; i < n_chan; i++) {
+ if (!devpriv->is_6143 && (list[i] & CR_ALT_SOURCE))
+ chan = devpriv->ai_calib_source;
+ else
+ chan = CR_CHAN(list[i]);
+ aref = CR_AREF(list[i]);
+ range = CR_RANGE(list[i]);
+ dither = (list[i] & CR_ALT_FILTER) != 0;
+
+ /* fix the external/internal range differences */
+ range = ni_gainlkup[board->gainlkup][range];
+ if (devpriv->is_611x)
+ devpriv->ai_offset[i] = offset;
+ else
+ devpriv->ai_offset[i] = (range & 0x100) ? 0 : offset;
+
+ hi = 0;
+ if ((list[i] & CR_ALT_SOURCE)) {
+ if (devpriv->is_611x)
+ ni_writew(dev, CR_CHAN(list[i]) & 0x0003,
+ Calibration_Channel_Select_611x);
+ } else {
+ if (devpriv->is_611x)
+ aref = AREF_DIFF;
+ else if (devpriv->is_6143)
+ aref = AREF_OTHER;
+ switch (aref) {
+ case AREF_DIFF:
+ hi |= AI_DIFFERENTIAL;
+ break;
+ case AREF_COMMON:
+ hi |= AI_COMMON;
+ break;
+ case AREF_GROUND:
+ hi |= AI_GROUND;
+ break;
+ case AREF_OTHER:
+ break;
+ }
+ }
+ hi |= AI_CONFIG_CHANNEL(chan);
+
+ ni_writew(dev, hi, Configuration_Memory_High);
+
+ if (!devpriv->is_6143) {
+ lo = range;
+ if (i == n_chan - 1)
+ lo |= AI_LAST_CHANNEL;
+ if (dither)
+ lo |= AI_DITHER;
+
+ ni_writew(dev, lo, Configuration_Memory_Low);
+ }
+ }
+
+ /* prime the channel/gain list */
+ if (!devpriv->is_611x && !devpriv->is_6143)
+ ni_prime_channelgain_list(dev);
+}
+
+static int ni_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int mask = (s->maxdata + 1) >> 1;
+ int i, n;
+ unsigned signbits;
+ unsigned int d;
+ unsigned long dl;
+
+ ni_load_channelgain_list(dev, s, 1, &insn->chanspec);
+
+ ni_clear_ai_fifo(dev);
+
+ signbits = devpriv->ai_offset[0];
+ if (devpriv->is_611x) {
+ for (n = 0; n < num_adc_stages_611x; n++) {
+ ni_stc_writew(dev, AI_CONVERT_Pulse,
+ AI_Command_1_Register);
+ udelay(1);
+ }
+ for (n = 0; n < insn->n; n++) {
+ ni_stc_writew(dev, AI_CONVERT_Pulse,
+ AI_Command_1_Register);
+ /* The 611x has screwy 32-bit FIFOs. */
+ d = 0;
+ for (i = 0; i < NI_TIMEOUT; i++) {
+ if (ni_readb(dev, XXX_Status) & 0x80) {
+ d = ni_readl(dev, ADC_FIFO_Data_611x);
+ d >>= 16;
+ d &= 0xffff;
+ break;
+ }
+ if (!(ni_stc_readw(dev, AI_Status_1_Register) &
+ AI_FIFO_Empty_St)) {
+ d = ni_readl(dev, ADC_FIFO_Data_611x);
+ d &= 0xffff;
+ break;
+ }
+ }
+ if (i == NI_TIMEOUT) {
+ dev_err(dev->class_dev, "timeout\n");
+ return -ETIME;
+ }
+ d += signbits;
+ data[n] = d;
+ }
+ } else if (devpriv->is_6143) {
+ for (n = 0; n < insn->n; n++) {
+ ni_stc_writew(dev, AI_CONVERT_Pulse,
+ AI_Command_1_Register);
+
+ /* The 6143 has 32-bit FIFOs. You need to strobe a bit to move a single 16bit stranded sample into the FIFO */
+ dl = 0;
+ for (i = 0; i < NI_TIMEOUT; i++) {
+ if (ni_readl(dev, AIFIFO_Status_6143) & 0x01) {
+ /* Get stranded sample into FIFO */
+ ni_writel(dev, 0x01,
+ AIFIFO_Control_6143);
+ dl = ni_readl(dev, AIFIFO_Data_6143);
+ break;
+ }
+ }
+ if (i == NI_TIMEOUT) {
+ dev_err(dev->class_dev, "timeout\n");
+ return -ETIME;
+ }
+ data[n] = (((dl >> 16) & 0xFFFF) + signbits) & 0xFFFF;
+ }
+ } else {
+ for (n = 0; n < insn->n; n++) {
+ ni_stc_writew(dev, AI_CONVERT_Pulse,
+ AI_Command_1_Register);
+ for (i = 0; i < NI_TIMEOUT; i++) {
+ if (!(ni_stc_readw(dev, AI_Status_1_Register) &
+ AI_FIFO_Empty_St))
+ break;
+ }
+ if (i == NI_TIMEOUT) {
+ dev_err(dev->class_dev, "timeout\n");
+ return -ETIME;
+ }
+ if (devpriv->is_m_series) {
+ dl = ni_readl(dev, M_Offset_AI_FIFO_Data);
+ dl &= mask;
+ data[n] = dl;
+ } else {
+ d = ni_readw(dev, ADC_FIFO_Data_Register);
+ d += signbits; /* subtle: needs to be short addition */
+ data[n] = d;
+ }
+ }
+ }
+ return insn->n;
+}
+
+static int ni_ns_to_timer(const struct comedi_device *dev, unsigned nanosec,
+ unsigned int flags)
+{
+ struct ni_private *devpriv = dev->private;
+ int divider;
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ divider = (nanosec + devpriv->clock_ns / 2) / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_DOWN:
+ divider = (nanosec) / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_UP:
+ divider = (nanosec + devpriv->clock_ns - 1) / devpriv->clock_ns;
+ break;
+ }
+ return divider - 1;
+}
+
+static unsigned ni_timer_to_ns(const struct comedi_device *dev, int timer)
+{
+ struct ni_private *devpriv = dev->private;
+
+ return devpriv->clock_ns * (timer + 1);
+}
+
+static unsigned ni_min_ai_scan_period_ns(struct comedi_device *dev,
+ unsigned num_channels)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+
+ /* simultaneously-sampled inputs */
+ if (devpriv->is_611x || devpriv->is_6143)
+ return board->ai_speed;
+
+ /* multiplexed inputs */
+ return board->ai_speed * num_channels;
+}
+
+static int ni_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+ unsigned int sources;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_INT | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+
+ sources = TRIG_TIMER | TRIG_EXT;
+ if (devpriv->is_611x || devpriv->is_6143)
+ sources |= TRIG_NOW;
+ err |= comedi_check_trigger_src(&cmd->convert_src, sources);
+
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_INT:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ tmp = CR_CHAN(cmd->start_arg);
+
+ if (tmp > 16)
+ tmp = 16;
+ tmp |= (cmd->start_arg & (CR_INVERT | CR_EDGE));
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, tmp);
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ ni_min_ai_scan_period_ns(dev, cmd->chanlist_len));
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ devpriv->clock_ns *
+ 0xffffff);
+ } else if (cmd->scan_begin_src == TRIG_EXT) {
+ /* external trigger */
+ unsigned int tmp = CR_CHAN(cmd->scan_begin_arg);
+
+ if (tmp > 16)
+ tmp = 16;
+ tmp |= (cmd->scan_begin_arg & (CR_INVERT | CR_EDGE));
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, tmp);
+ } else { /* TRIG_OTHER */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ if (devpriv->is_611x || devpriv->is_6143) {
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg,
+ 0);
+ } else {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_speed);
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg,
+ devpriv->clock_ns *
+ 0xffff);
+ }
+ } else if (cmd->convert_src == TRIG_EXT) {
+ /* external trigger */
+ unsigned int tmp = CR_CHAN(cmd->convert_arg);
+
+ if (tmp > 16)
+ tmp = 16;
+ tmp |= (cmd->convert_arg & (CR_ALT_FILTER | CR_INVERT));
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, tmp);
+ } else if (cmd->convert_src == TRIG_NOW) {
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ unsigned int max_count = 0x01000000;
+
+ if (devpriv->is_611x)
+ max_count -= num_adc_stages_611x;
+ err |= comedi_check_trigger_arg_max(&cmd->stop_arg, max_count);
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ } else {
+ /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ tmp = cmd->scan_begin_arg;
+ cmd->scan_begin_arg =
+ ni_timer_to_ns(dev, ni_ns_to_timer(dev,
+ cmd->scan_begin_arg,
+ cmd->flags));
+ if (tmp != cmd->scan_begin_arg)
+ err++;
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ if (!devpriv->is_611x && !devpriv->is_6143) {
+ tmp = cmd->convert_arg;
+ cmd->convert_arg =
+ ni_timer_to_ns(dev, ni_ns_to_timer(dev,
+ cmd->convert_arg,
+ cmd->flags));
+ if (tmp != cmd->convert_arg)
+ err++;
+ if (cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->scan_begin_arg <
+ cmd->convert_arg * cmd->scan_end_arg) {
+ cmd->scan_begin_arg =
+ cmd->convert_arg * cmd->scan_end_arg;
+ err++;
+ }
+ }
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int ni_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ ni_stc_writew(dev, AI_START1_Pulse | devpriv->ai_cmd2,
+ AI_Command_2_Register);
+ s->async->inttrig = NULL;
+
+ return 1;
+}
+
+static int ni_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+ const struct comedi_cmd *cmd = &s->async->cmd;
+ int timer;
+ int mode1 = 0; /* mode1 is needed for both stop and convert */
+ int mode2 = 0;
+ int start_stop_select = 0;
+ unsigned int stop_count;
+ int interrupt_a_enable = 0;
+
+ if (dev->irq == 0) {
+ dev_err(dev->class_dev, "cannot run command without an irq\n");
+ return -EIO;
+ }
+ ni_clear_ai_fifo(dev);
+
+ ni_load_channelgain_list(dev, s, cmd->chanlist_len, cmd->chanlist);
+
+ /* start configuration */
+ ni_stc_writew(dev, AI_Configuration_Start, Joint_Reset_Register);
+
+ /* disable analog triggering for now, since it
+ * interferes with the use of pfi0 */
+ devpriv->an_trig_etc_reg &= ~Analog_Trigger_Enable;
+ ni_stc_writew(dev, devpriv->an_trig_etc_reg,
+ Analog_Trigger_Etc_Register);
+
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ case TRIG_NOW:
+ ni_stc_writew(dev,
+ AI_START2_Select(0) |
+ AI_START1_Sync | AI_START1_Edge |
+ AI_START1_Select(0),
+ AI_Trigger_Select_Register);
+ break;
+ case TRIG_EXT:
+ {
+ int chan = CR_CHAN(cmd->start_arg);
+ unsigned int bits = AI_START2_Select(0) |
+ AI_START1_Sync | AI_START1_Select(chan + 1);
+
+ if (cmd->start_arg & CR_INVERT)
+ bits |= AI_START1_Polarity;
+ if (cmd->start_arg & CR_EDGE)
+ bits |= AI_START1_Edge;
+ ni_stc_writew(dev, bits, AI_Trigger_Select_Register);
+ break;
+ }
+ }
+
+ mode2 &= ~AI_Pre_Trigger;
+ mode2 &= ~AI_SC_Initial_Load_Source;
+ mode2 &= ~AI_SC_Reload_Mode;
+ ni_stc_writew(dev, mode2, AI_Mode_2_Register);
+
+ if (cmd->chanlist_len == 1 || devpriv->is_611x || devpriv->is_6143) {
+ start_stop_select |= AI_STOP_Polarity;
+ start_stop_select |= AI_STOP_Select(31); /* logic low */
+ start_stop_select |= AI_STOP_Sync;
+ } else {
+ start_stop_select |= AI_STOP_Select(19); /* ai configuration memory */
+ }
+ ni_stc_writew(dev, start_stop_select, AI_START_STOP_Select_Register);
+
+ devpriv->ai_cmd2 = 0;
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ stop_count = cmd->stop_arg - 1;
+
+ if (devpriv->is_611x) {
+ /* have to take 3 stage adc pipeline into account */
+ stop_count += num_adc_stages_611x;
+ }
+ /* stage number of scans */
+ ni_stc_writel(dev, stop_count, AI_SC_Load_A_Registers);
+
+ mode1 |= AI_Start_Stop | AI_Mode_1_Reserved | AI_Trigger_Once;
+ ni_stc_writew(dev, mode1, AI_Mode_1_Register);
+ /* load SC (Scan Count) */
+ ni_stc_writew(dev, AI_SC_Load, AI_Command_1_Register);
+
+ if (stop_count == 0) {
+ devpriv->ai_cmd2 |= AI_End_On_End_Of_Scan;
+ interrupt_a_enable |= AI_STOP_Interrupt_Enable;
+ /* this is required to get the last sample for chanlist_len > 1, not sure why */
+ if (cmd->chanlist_len > 1)
+ start_stop_select |=
+ AI_STOP_Polarity | AI_STOP_Edge;
+ }
+ break;
+ case TRIG_NONE:
+ /* stage number of scans */
+ ni_stc_writel(dev, 0, AI_SC_Load_A_Registers);
+
+ mode1 |= AI_Start_Stop | AI_Mode_1_Reserved | AI_Continuous;
+ ni_stc_writew(dev, mode1, AI_Mode_1_Register);
+
+ /* load SC (Scan Count) */
+ ni_stc_writew(dev, AI_SC_Load, AI_Command_1_Register);
+ break;
+ }
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ /*
+ stop bits for non 611x boards
+ AI_SI_Special_Trigger_Delay=0
+ AI_Pre_Trigger=0
+ AI_START_STOP_Select_Register:
+ AI_START_Polarity=0 (?) rising edge
+ AI_START_Edge=1 edge triggered
+ AI_START_Sync=1 (?)
+ AI_START_Select=0 SI_TC
+ AI_STOP_Polarity=0 rising edge
+ AI_STOP_Edge=0 level
+ AI_STOP_Sync=1
+ AI_STOP_Select=19 external pin (configuration mem)
+ */
+ start_stop_select |= AI_START_Edge | AI_START_Sync;
+ ni_stc_writew(dev, start_stop_select,
+ AI_START_STOP_Select_Register);
+
+ mode2 |= AI_SI_Reload_Mode(0);
+ /* AI_SI_Initial_Load_Source=A */
+ mode2 &= ~AI_SI_Initial_Load_Source;
+ /* mode2 |= AI_SC_Reload_Mode; */
+ ni_stc_writew(dev, mode2, AI_Mode_2_Register);
+
+ /* load SI */
+ timer = ni_ns_to_timer(dev, cmd->scan_begin_arg,
+ CMDF_ROUND_NEAREST);
+ ni_stc_writel(dev, timer, AI_SI_Load_A_Registers);
+ ni_stc_writew(dev, AI_SI_Load, AI_Command_1_Register);
+ break;
+ case TRIG_EXT:
+ if (cmd->scan_begin_arg & CR_EDGE)
+ start_stop_select |= AI_START_Edge;
+ /* AI_START_Polarity==1 is falling edge */
+ if (cmd->scan_begin_arg & CR_INVERT)
+ start_stop_select |= AI_START_Polarity;
+ if (cmd->scan_begin_src != cmd->convert_src ||
+ (cmd->scan_begin_arg & ~CR_EDGE) !=
+ (cmd->convert_arg & ~CR_EDGE))
+ start_stop_select |= AI_START_Sync;
+ start_stop_select |=
+ AI_START_Select(1 + CR_CHAN(cmd->scan_begin_arg));
+ ni_stc_writew(dev, start_stop_select,
+ AI_START_STOP_Select_Register);
+ break;
+ }
+
+ switch (cmd->convert_src) {
+ case TRIG_TIMER:
+ case TRIG_NOW:
+ if (cmd->convert_arg == 0 || cmd->convert_src == TRIG_NOW)
+ timer = 1;
+ else
+ timer = ni_ns_to_timer(dev, cmd->convert_arg,
+ CMDF_ROUND_NEAREST);
+ /* 0,0 does not work */
+ ni_stc_writew(dev, 1, AI_SI2_Load_A_Register);
+ ni_stc_writew(dev, timer, AI_SI2_Load_B_Register);
+
+ /* AI_SI2_Reload_Mode = alternate */
+ /* AI_SI2_Initial_Load_Source = A */
+ mode2 &= ~AI_SI2_Initial_Load_Source;
+ mode2 |= AI_SI2_Reload_Mode;
+ ni_stc_writew(dev, mode2, AI_Mode_2_Register);
+
+ /* AI_SI2_Load */
+ ni_stc_writew(dev, AI_SI2_Load, AI_Command_1_Register);
+
+ mode2 |= AI_SI2_Reload_Mode; /* alternate */
+ mode2 |= AI_SI2_Initial_Load_Source; /* B */
+
+ ni_stc_writew(dev, mode2, AI_Mode_2_Register);
+ break;
+ case TRIG_EXT:
+ mode1 |= AI_CONVERT_Source_Select(1 + cmd->convert_arg);
+ if ((cmd->convert_arg & CR_INVERT) == 0)
+ mode1 |= AI_CONVERT_Source_Polarity;
+ ni_stc_writew(dev, mode1, AI_Mode_1_Register);
+
+ mode2 |= AI_Start_Stop_Gate_Enable | AI_SC_Gate_Enable;
+ ni_stc_writew(dev, mode2, AI_Mode_2_Register);
+
+ break;
+ }
+
+ if (dev->irq) {
+ /* interrupt on FIFO, errors, SC_TC */
+ interrupt_a_enable |= AI_Error_Interrupt_Enable |
+ AI_SC_TC_Interrupt_Enable;
+
+#ifndef PCIDMA
+ interrupt_a_enable |= AI_FIFO_Interrupt_Enable;
+#endif
+
+ if (cmd->flags & CMDF_WAKE_EOS
+ || (devpriv->ai_cmd2 & AI_End_On_End_Of_Scan)) {
+ /* wake on end-of-scan */
+ devpriv->aimode = AIMODE_SCAN;
+ } else {
+ devpriv->aimode = AIMODE_HALF_FULL;
+ }
+
+ switch (devpriv->aimode) {
+ case AIMODE_HALF_FULL:
+ /*generate FIFO interrupts and DMA requests on half-full */
+#ifdef PCIDMA
+ ni_stc_writew(dev, AI_FIFO_Mode_HF_to_E,
+ AI_Mode_3_Register);
+#else
+ ni_stc_writew(dev, AI_FIFO_Mode_HF,
+ AI_Mode_3_Register);
+#endif
+ break;
+ case AIMODE_SAMPLE:
+ /*generate FIFO interrupts on non-empty */
+ ni_stc_writew(dev, AI_FIFO_Mode_NE,
+ AI_Mode_3_Register);
+ break;
+ case AIMODE_SCAN:
+#ifdef PCIDMA
+ ni_stc_writew(dev, AI_FIFO_Mode_NE,
+ AI_Mode_3_Register);
+#else
+ ni_stc_writew(dev, AI_FIFO_Mode_HF,
+ AI_Mode_3_Register);
+#endif
+ interrupt_a_enable |= AI_STOP_Interrupt_Enable;
+ break;
+ default:
+ break;
+ }
+
+ /* clear interrupts */
+ ni_stc_writew(dev,
+ AI_Error_Interrupt_Ack |
+ AI_STOP_Interrupt_Ack |
+ AI_START_Interrupt_Ack |
+ AI_START2_Interrupt_Ack |
+ AI_START1_Interrupt_Ack |
+ AI_SC_TC_Interrupt_Ack |
+ AI_SC_TC_Error_Confirm,
+ Interrupt_A_Ack_Register);
+
+ ni_set_bits(dev, Interrupt_A_Enable_Register,
+ interrupt_a_enable, 1);
+ } else {
+ /* interrupt on nothing */
+ ni_set_bits(dev, Interrupt_A_Enable_Register, ~0, 0);
+
+ /* XXX start polling if necessary */
+ }
+
+ /* end configuration */
+ ni_stc_writew(dev, AI_Configuration_End, Joint_Reset_Register);
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ ni_stc_writew(dev,
+ AI_SI2_Arm | AI_SI_Arm | AI_DIV_Arm | AI_SC_Arm,
+ AI_Command_1_Register);
+ break;
+ case TRIG_EXT:
+ /* XXX AI_SI_Arm? */
+ ni_stc_writew(dev,
+ AI_SI2_Arm | AI_SI_Arm | AI_DIV_Arm | AI_SC_Arm,
+ AI_Command_1_Register);
+ break;
+ }
+
+#ifdef PCIDMA
+ {
+ int retval = ni_ai_setup_MITE_dma(dev);
+
+ if (retval)
+ return retval;
+ }
+#endif
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* AI_START1_Pulse */
+ ni_stc_writew(dev, AI_START1_Pulse | devpriv->ai_cmd2,
+ AI_Command_2_Register);
+ s->async->inttrig = NULL;
+ } else if (cmd->start_src == TRIG_EXT) {
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = ni_ai_inttrig;
+ }
+
+ return 0;
+}
+
+static int ni_ai_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (insn->n < 1)
+ return -EINVAL;
+
+ switch (data[0]) {
+ case INSN_CONFIG_ALT_SOURCE:
+ if (devpriv->is_m_series) {
+ if (data[1] & ~(MSeries_AI_Bypass_Cal_Sel_Pos_Mask |
+ MSeries_AI_Bypass_Cal_Sel_Neg_Mask |
+ MSeries_AI_Bypass_Mode_Mux_Mask |
+ MSeries_AO_Bypass_AO_Cal_Sel_Mask)) {
+ return -EINVAL;
+ }
+ devpriv->ai_calib_source = data[1];
+ } else if (devpriv->is_6143) {
+ unsigned int calib_source;
+
+ calib_source = data[1] & 0xf;
+
+ devpriv->ai_calib_source = calib_source;
+ ni_writew(dev, calib_source, Calibration_Channel_6143);
+ } else {
+ unsigned int calib_source;
+ unsigned int calib_source_adjust;
+
+ calib_source = data[1] & 0xf;
+ calib_source_adjust = (data[1] >> 4) & 0xff;
+
+ if (calib_source >= 8)
+ return -EINVAL;
+ devpriv->ai_calib_source = calib_source;
+ if (devpriv->is_611x) {
+ ni_writeb(dev, calib_source_adjust,
+ Cal_Gain_Select_611x);
+ }
+ }
+ return 2;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static void ni_ao_munge(struct comedi_device *dev, struct comedi_subdevice *s,
+ void *data, unsigned int num_bytes,
+ unsigned int chan_index)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int nsamples = comedi_bytes_to_samples(s, num_bytes);
+ unsigned short *array = data;
+ unsigned int i;
+
+ for (i = 0; i < nsamples; i++) {
+ unsigned int range = CR_RANGE(cmd->chanlist[chan_index]);
+ unsigned short val = array[i];
+
+ /*
+ * Munge data from unsigned to two's complement for
+ * bipolar ranges.
+ */
+ if (comedi_range_is_bipolar(s, range))
+ val = comedi_offset_munge(s, val);
+#ifdef PCIDMA
+ val = cpu_to_le16(val);
+#endif
+ array[i] = val;
+
+ chan_index++;
+ chan_index %= cmd->chanlist_len;
+ }
+}
+
+static int ni_m_series_ao_config_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec[],
+ unsigned int n_chans, int timed)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int range;
+ unsigned int chan;
+ unsigned int conf;
+ int i;
+ int invert = 0;
+
+ if (timed) {
+ for (i = 0; i < s->n_chan; ++i) {
+ devpriv->ao_conf[i] &= ~MSeries_AO_Update_Timed_Bit;
+ ni_writeb(dev, devpriv->ao_conf[i],
+ M_Offset_AO_Config_Bank(i));
+ ni_writeb(dev, 0xf, M_Offset_AO_Waveform_Order(i));
+ }
+ }
+ for (i = 0; i < n_chans; i++) {
+ const struct comedi_krange *krange;
+
+ chan = CR_CHAN(chanspec[i]);
+ range = CR_RANGE(chanspec[i]);
+ krange = s->range_table->range + range;
+ invert = 0;
+ conf = 0;
+ switch (krange->max - krange->min) {
+ case 20000000:
+ conf |= MSeries_AO_DAC_Reference_10V_Internal_Bits;
+ ni_writeb(dev, 0,
+ M_Offset_AO_Reference_Attenuation(chan));
+ break;
+ case 10000000:
+ conf |= MSeries_AO_DAC_Reference_5V_Internal_Bits;
+ ni_writeb(dev, 0,
+ M_Offset_AO_Reference_Attenuation(chan));
+ break;
+ case 4000000:
+ conf |= MSeries_AO_DAC_Reference_10V_Internal_Bits;
+ ni_writeb(dev, MSeries_Attenuate_x5_Bit,
+ M_Offset_AO_Reference_Attenuation(chan));
+ break;
+ case 2000000:
+ conf |= MSeries_AO_DAC_Reference_5V_Internal_Bits;
+ ni_writeb(dev, MSeries_Attenuate_x5_Bit,
+ M_Offset_AO_Reference_Attenuation(chan));
+ break;
+ default:
+ dev_err(dev->class_dev,
+ "bug! unhandled ao reference voltage\n");
+ break;
+ }
+ switch (krange->max + krange->min) {
+ case 0:
+ conf |= MSeries_AO_DAC_Offset_0V_Bits;
+ break;
+ case 10000000:
+ conf |= MSeries_AO_DAC_Offset_5V_Bits;
+ break;
+ default:
+ dev_err(dev->class_dev,
+ "bug! unhandled ao offset voltage\n");
+ break;
+ }
+ if (timed)
+ conf |= MSeries_AO_Update_Timed_Bit;
+ ni_writeb(dev, conf, M_Offset_AO_Config_Bank(chan));
+ devpriv->ao_conf[chan] = conf;
+ ni_writeb(dev, i, M_Offset_AO_Waveform_Order(chan));
+ }
+ return invert;
+}
+
+static int ni_old_ao_config_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec[],
+ unsigned int n_chans)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int range;
+ unsigned int chan;
+ unsigned int conf;
+ int i;
+ int invert = 0;
+
+ for (i = 0; i < n_chans; i++) {
+ chan = CR_CHAN(chanspec[i]);
+ range = CR_RANGE(chanspec[i]);
+ conf = AO_Channel(chan);
+
+ if (comedi_range_is_bipolar(s, range)) {
+ conf |= AO_Bipolar;
+ invert = (s->maxdata + 1) >> 1;
+ } else {
+ invert = 0;
+ }
+ if (comedi_range_is_external(s, range))
+ conf |= AO_Ext_Ref;
+
+ /* not all boards can deglitch, but this shouldn't hurt */
+ if (chanspec[i] & CR_DEGLITCH)
+ conf |= AO_Deglitch;
+
+ /* analog reference */
+ /* AREF_OTHER connects AO ground to AI ground, i think */
+ conf |= (CR_AREF(chanspec[i]) ==
+ AREF_OTHER) ? AO_Ground_Ref : 0;
+
+ ni_writew(dev, conf, AO_Configuration);
+ devpriv->ao_conf[chan] = conf;
+ }
+ return invert;
+}
+
+static int ni_ao_config_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec[], unsigned int n_chans,
+ int timed)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (devpriv->is_m_series)
+ return ni_m_series_ao_config_chanlist(dev, s, chanspec, n_chans,
+ timed);
+ else
+ return ni_old_ao_config_chanlist(dev, s, chanspec, n_chans);
+}
+
+static int ni_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int reg;
+ int i;
+
+ if (devpriv->is_6xxx) {
+ ni_ao_win_outw(dev, 1 << chan, AO_Immediate_671x);
+
+ reg = DACx_Direct_Data_671x(chan);
+ } else if (devpriv->is_m_series) {
+ reg = M_Offset_DAC_Direct_Data(chan);
+ } else {
+ reg = (chan) ? DAC1_Direct_Data : DAC0_Direct_Data;
+ }
+
+ ni_ao_config_chanlist(dev, s, &insn->chanspec, 1, 0);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ if (devpriv->is_6xxx) {
+ /*
+ * 6xxx boards have bipolar outputs, munge the
+ * unsigned comedi values to 2's complement
+ */
+ val = comedi_offset_munge(s, val);
+
+ ni_ao_win_outw(dev, val, reg);
+ } else if (devpriv->is_m_series) {
+ /*
+ * M-series boards use offset binary values for
+ * bipolar and uinpolar outputs
+ */
+ ni_writew(dev, val, reg);
+ } else {
+ /*
+ * Non-M series boards need two's complement values
+ * for bipolar ranges.
+ */
+ if (comedi_range_is_bipolar(s, range))
+ val = comedi_offset_munge(s, val);
+
+ ni_writew(dev, val, reg);
+ }
+ }
+
+ return insn->n;
+}
+
+static int ni_ao_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ unsigned int nbytes;
+
+ switch (data[0]) {
+ case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE:
+ switch (data[1]) {
+ case COMEDI_OUTPUT:
+ nbytes = comedi_samples_to_bytes(s,
+ board->ao_fifo_depth);
+ data[2] = 1 + nbytes;
+ if (devpriv->mite)
+ data[2] += devpriv->mite->fifo_size;
+ break;
+ case COMEDI_INPUT:
+ data[2] = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int ni_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+ int interrupt_b_bits;
+ int i;
+ static const int timeout = 1000;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ /* Null trig at beginning prevent ao start trigger from executing more than
+ once per command (and doing things like trying to allocate the ao dma channel
+ multiple times) */
+ s->async->inttrig = NULL;
+
+ ni_set_bits(dev, Interrupt_B_Enable_Register,
+ AO_FIFO_Interrupt_Enable | AO_Error_Interrupt_Enable, 0);
+ interrupt_b_bits = AO_Error_Interrupt_Enable;
+#ifdef PCIDMA
+ ni_stc_writew(dev, 1, DAC_FIFO_Clear);
+ if (devpriv->is_6xxx)
+ ni_ao_win_outl(dev, 0x6, AO_FIFO_Offset_Load_611x);
+ ret = ni_ao_setup_MITE_dma(dev);
+ if (ret)
+ return ret;
+ ret = ni_ao_wait_for_dma_load(dev);
+ if (ret < 0)
+ return ret;
+#else
+ ret = ni_ao_prep_fifo(dev, s);
+ if (ret == 0)
+ return -EPIPE;
+
+ interrupt_b_bits |= AO_FIFO_Interrupt_Enable;
+#endif
+
+ ni_stc_writew(dev, devpriv->ao_mode3 | AO_Not_An_UPDATE,
+ AO_Mode_3_Register);
+ ni_stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register);
+ /* wait for DACs to be loaded */
+ for (i = 0; i < timeout; i++) {
+ udelay(1);
+ if ((ni_stc_readw(dev, Joint_Status_2_Register) &
+ AO_TMRDACWRs_In_Progress_St) == 0)
+ break;
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev,
+ "timed out waiting for AO_TMRDACWRs_In_Progress_St to clear\n");
+ return -EIO;
+ }
+ /*
+ * stc manual says we are need to clear error interrupt after
+ * AO_TMRDACWRs_In_Progress_St clears
+ */
+ ni_stc_writew(dev, AO_Error_Interrupt_Ack, Interrupt_B_Ack_Register);
+
+ ni_set_bits(dev, Interrupt_B_Enable_Register, interrupt_b_bits, 1);
+
+ ni_stc_writew(dev, devpriv->ao_cmd1 |
+ AO_UI_Arm | AO_UC_Arm | AO_BC_Arm |
+ AO_DAC1_Update_Mode | AO_DAC0_Update_Mode,
+ AO_Command_1_Register);
+
+ ni_stc_writew(dev, devpriv->ao_cmd2 | AO_START1_Pulse,
+ AO_Command_2_Register);
+
+ return 0;
+}
+
+static int ni_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ const struct comedi_cmd *cmd = &s->async->cmd;
+ int bits;
+ int i;
+ unsigned trigvar;
+
+ if (dev->irq == 0) {
+ dev_err(dev->class_dev, "cannot run command without an irq\n");
+ return -EIO;
+ }
+
+ ni_stc_writew(dev, AO_Configuration_Start, Joint_Reset_Register);
+
+ ni_stc_writew(dev, AO_Disarm, AO_Command_1_Register);
+
+ if (devpriv->is_6xxx) {
+ ni_ao_win_outw(dev, CLEAR_WG, AO_Misc_611x);
+
+ bits = 0;
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ int chan;
+
+ chan = CR_CHAN(cmd->chanlist[i]);
+ bits |= 1 << chan;
+ ni_ao_win_outw(dev, chan, AO_Waveform_Generation_611x);
+ }
+ ni_ao_win_outw(dev, bits, AO_Timed_611x);
+ }
+
+ ni_ao_config_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len, 1);
+
+ if (cmd->stop_src == TRIG_NONE) {
+ devpriv->ao_mode1 |= AO_Continuous;
+ devpriv->ao_mode1 &= ~AO_Trigger_Once;
+ } else {
+ devpriv->ao_mode1 &= ~AO_Continuous;
+ devpriv->ao_mode1 |= AO_Trigger_Once;
+ }
+ ni_stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register);
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ case TRIG_NOW:
+ devpriv->ao_trigger_select &=
+ ~(AO_START1_Polarity | AO_START1_Select(-1));
+ devpriv->ao_trigger_select |= AO_START1_Edge | AO_START1_Sync;
+ ni_stc_writew(dev, devpriv->ao_trigger_select,
+ AO_Trigger_Select_Register);
+ break;
+ case TRIG_EXT:
+ devpriv->ao_trigger_select =
+ AO_START1_Select(CR_CHAN(cmd->start_arg) + 1);
+ if (cmd->start_arg & CR_INVERT)
+ devpriv->ao_trigger_select |= AO_START1_Polarity; /* 0=active high, 1=active low. see daq-stc 3-24 (p186) */
+ if (cmd->start_arg & CR_EDGE)
+ devpriv->ao_trigger_select |= AO_START1_Edge; /* 0=edge detection disabled, 1=enabled */
+ ni_stc_writew(dev, devpriv->ao_trigger_select,
+ AO_Trigger_Select_Register);
+ break;
+ default:
+ BUG();
+ break;
+ }
+ devpriv->ao_mode3 &= ~AO_Trigger_Length;
+ ni_stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register);
+
+ ni_stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register);
+ devpriv->ao_mode2 &= ~AO_BC_Initial_Load_Source;
+ ni_stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register);
+ if (cmd->stop_src == TRIG_NONE)
+ ni_stc_writel(dev, 0xffffff, AO_BC_Load_A_Register);
+ else
+ ni_stc_writel(dev, 0, AO_BC_Load_A_Register);
+ ni_stc_writew(dev, AO_BC_Load, AO_Command_1_Register);
+ devpriv->ao_mode2 &= ~AO_UC_Initial_Load_Source;
+ ni_stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register);
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ if (devpriv->is_m_series) {
+ /* this is how the NI example code does it for m-series boards, verified correct with 6259 */
+ ni_stc_writel(dev, cmd->stop_arg - 1,
+ AO_UC_Load_A_Register);
+ ni_stc_writew(dev, AO_UC_Load, AO_Command_1_Register);
+ } else {
+ ni_stc_writel(dev, cmd->stop_arg,
+ AO_UC_Load_A_Register);
+ ni_stc_writew(dev, AO_UC_Load, AO_Command_1_Register);
+ ni_stc_writel(dev, cmd->stop_arg - 1,
+ AO_UC_Load_A_Register);
+ }
+ break;
+ case TRIG_NONE:
+ ni_stc_writel(dev, 0xffffff, AO_UC_Load_A_Register);
+ ni_stc_writew(dev, AO_UC_Load, AO_Command_1_Register);
+ ni_stc_writel(dev, 0xffffff, AO_UC_Load_A_Register);
+ break;
+ default:
+ ni_stc_writel(dev, 0, AO_UC_Load_A_Register);
+ ni_stc_writew(dev, AO_UC_Load, AO_Command_1_Register);
+ ni_stc_writel(dev, cmd->stop_arg, AO_UC_Load_A_Register);
+ }
+
+ devpriv->ao_mode1 &=
+ ~(AO_UI_Source_Select(0x1f) | AO_UI_Source_Polarity |
+ AO_UPDATE_Source_Select(0x1f) | AO_UPDATE_Source_Polarity);
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ devpriv->ao_cmd2 &= ~AO_BC_Gate_Enable;
+ trigvar =
+ ni_ns_to_timer(dev, cmd->scan_begin_arg,
+ CMDF_ROUND_NEAREST);
+ ni_stc_writel(dev, 1, AO_UI_Load_A_Register);
+ ni_stc_writew(dev, AO_UI_Load, AO_Command_1_Register);
+ ni_stc_writel(dev, trigvar, AO_UI_Load_A_Register);
+ break;
+ case TRIG_EXT:
+ devpriv->ao_mode1 |=
+ AO_UPDATE_Source_Select(cmd->scan_begin_arg);
+ if (cmd->scan_begin_arg & CR_INVERT)
+ devpriv->ao_mode1 |= AO_UPDATE_Source_Polarity;
+ devpriv->ao_cmd2 |= AO_BC_Gate_Enable;
+ break;
+ default:
+ BUG();
+ break;
+ }
+ ni_stc_writew(dev, devpriv->ao_cmd2, AO_Command_2_Register);
+ ni_stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register);
+ devpriv->ao_mode2 &=
+ ~(AO_UI_Reload_Mode(3) | AO_UI_Initial_Load_Source);
+ ni_stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register);
+
+ if (cmd->scan_end_arg > 1) {
+ devpriv->ao_mode1 |= AO_Multiple_Channels;
+ ni_stc_writew(dev,
+ AO_Number_Of_Channels(cmd->scan_end_arg - 1) |
+ AO_UPDATE_Output_Select(AO_Update_Output_High_Z),
+ AO_Output_Control_Register);
+ } else {
+ unsigned bits;
+
+ devpriv->ao_mode1 &= ~AO_Multiple_Channels;
+ bits = AO_UPDATE_Output_Select(AO_Update_Output_High_Z);
+ if (devpriv->is_m_series || devpriv->is_6xxx) {
+ bits |= AO_Number_Of_Channels(0);
+ } else {
+ bits |=
+ AO_Number_Of_Channels(CR_CHAN(cmd->chanlist[0]));
+ }
+ ni_stc_writew(dev, bits, AO_Output_Control_Register);
+ }
+ ni_stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register);
+
+ ni_stc_writew(dev, AO_DAC0_Update_Mode | AO_DAC1_Update_Mode,
+ AO_Command_1_Register);
+
+ devpriv->ao_mode3 |= AO_Stop_On_Overrun_Error;
+ ni_stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register);
+
+ devpriv->ao_mode2 &= ~AO_FIFO_Mode_Mask;
+#ifdef PCIDMA
+ devpriv->ao_mode2 |= AO_FIFO_Mode_HF_to_F;
+#else
+ devpriv->ao_mode2 |= AO_FIFO_Mode_HF;
+#endif
+ devpriv->ao_mode2 &= ~AO_FIFO_Retransmit_Enable;
+ ni_stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register);
+
+ bits = AO_BC_Source_Select | AO_UPDATE_Pulse_Width |
+ AO_TMRDACWR_Pulse_Width;
+ if (board->ao_fifo_depth)
+ bits |= AO_FIFO_Enable;
+ else
+ bits |= AO_DMA_PIO_Control;
+#if 0
+ /* F Hess: windows driver does not set AO_Number_Of_DAC_Packages bit for 6281,
+ verified with bus analyzer. */
+ if (devpriv->is_m_series)
+ bits |= AO_Number_Of_DAC_Packages;
+#endif
+ ni_stc_writew(dev, bits, AO_Personal_Register);
+ /* enable sending of ao dma requests */
+ ni_stc_writew(dev, AO_AOFREQ_Enable, AO_Start_Select_Register);
+
+ ni_stc_writew(dev, AO_Configuration_End, Joint_Reset_Register);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ ni_stc_writew(dev, AO_BC_TC_Interrupt_Ack,
+ Interrupt_B_Ack_Register);
+ ni_set_bits(dev, Interrupt_B_Enable_Register,
+ AO_BC_TC_Interrupt_Enable, 1);
+ }
+
+ s->async->inttrig = ni_ao_inttrig;
+
+ return 0;
+}
+
+static int ni_ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ tmp = CR_CHAN(cmd->start_arg);
+
+ if (tmp > 18)
+ tmp = 18;
+ tmp |= (cmd->start_arg & (CR_INVERT | CR_EDGE));
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, tmp);
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ board->ao_speed);
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ devpriv->clock_ns *
+ 0xffffff);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ tmp = cmd->scan_begin_arg;
+ cmd->scan_begin_arg =
+ ni_timer_to_ns(dev, ni_ns_to_timer(dev,
+ cmd->scan_begin_arg,
+ cmd->flags));
+ if (tmp != cmd->scan_begin_arg)
+ err++;
+ }
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int ni_ao_reset(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+
+ ni_release_ao_mite_channel(dev);
+
+ ni_stc_writew(dev, AO_Configuration_Start, Joint_Reset_Register);
+ ni_stc_writew(dev, AO_Disarm, AO_Command_1_Register);
+ ni_set_bits(dev, Interrupt_B_Enable_Register, ~0, 0);
+ ni_stc_writew(dev, AO_BC_Source_Select, AO_Personal_Register);
+ ni_stc_writew(dev, 0x3f98, Interrupt_B_Ack_Register);
+ ni_stc_writew(dev, AO_BC_Source_Select | AO_UPDATE_Pulse_Width |
+ AO_TMRDACWR_Pulse_Width, AO_Personal_Register);
+ ni_stc_writew(dev, 0, AO_Output_Control_Register);
+ ni_stc_writew(dev, 0, AO_Start_Select_Register);
+ devpriv->ao_cmd1 = 0;
+ ni_stc_writew(dev, devpriv->ao_cmd1, AO_Command_1_Register);
+ devpriv->ao_cmd2 = 0;
+ ni_stc_writew(dev, devpriv->ao_cmd2, AO_Command_2_Register);
+ devpriv->ao_mode1 = 0;
+ ni_stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register);
+ devpriv->ao_mode2 = 0;
+ ni_stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register);
+ if (devpriv->is_m_series)
+ devpriv->ao_mode3 = AO_Last_Gate_Disable;
+ else
+ devpriv->ao_mode3 = 0;
+ ni_stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register);
+ devpriv->ao_trigger_select = 0;
+ ni_stc_writew(dev, devpriv->ao_trigger_select,
+ AO_Trigger_Select_Register);
+ if (devpriv->is_6xxx) {
+ unsigned immediate_bits = 0;
+ unsigned i;
+
+ for (i = 0; i < s->n_chan; ++i)
+ immediate_bits |= 1 << i;
+ ni_ao_win_outw(dev, immediate_bits, AO_Immediate_671x);
+ ni_ao_win_outw(dev, CLEAR_WG, AO_Misc_611x);
+ }
+ ni_stc_writew(dev, AO_Configuration_End, Joint_Reset_Register);
+
+ return 0;
+}
+
+/* digital io */
+
+static int ni_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ devpriv->dio_control &= ~DIO_Pins_Dir_Mask;
+ devpriv->dio_control |= DIO_Pins_Dir(s->io_bits);
+ ni_stc_writew(dev, devpriv->dio_control, DIO_Control_Register);
+
+ return insn->n;
+}
+
+static int ni_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ /* Make sure we're not using the serial part of the dio */
+ if ((data[0] & (DIO_SDIN | DIO_SDOUT)) && devpriv->serial_interval_ns)
+ return -EBUSY;
+
+ if (comedi_dio_update_state(s, data)) {
+ devpriv->dio_output &= ~DIO_Parallel_Data_Mask;
+ devpriv->dio_output |= DIO_Parallel_Data_Out(s->state);
+ ni_stc_writew(dev, devpriv->dio_output, DIO_Output_Register);
+ }
+
+ data[1] = ni_stc_readw(dev, DIO_Parallel_Input_Register);
+
+ return insn->n;
+}
+
+static int ni_m_series_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ ni_writel(dev, s->io_bits, M_Offset_DIO_Direction);
+
+ return insn->n;
+}
+
+static int ni_m_series_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ ni_writel(dev, s->state, M_Offset_Static_Digital_Output);
+
+ data[1] = ni_readl(dev, M_Offset_Static_Digital_Input);
+
+ return insn->n;
+}
+
+static int ni_cdio_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; ++i) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != i)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ni_cdio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ tmp = cmd->scan_begin_arg;
+ tmp &= CR_PACK_FLAGS(CDO_Sample_Source_Select_Mask, 0, 0, CR_INVERT);
+ if (tmp != cmd->scan_begin_arg)
+ err |= -EINVAL;
+
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= ni_cdio_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int ni_cdo_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ const unsigned timeout = 1000;
+ int retval = 0;
+ unsigned i;
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+#endif
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+
+ /* read alloc the entire buffer */
+ comedi_buf_read_alloc(s, s->async->prealloc_bufsz);
+
+#ifdef PCIDMA
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->cdo_mite_chan) {
+ mite_prep_dma(devpriv->cdo_mite_chan, 32, 32);
+ mite_dma_arm(devpriv->cdo_mite_chan);
+ } else {
+ dev_err(dev->class_dev, "BUG: no cdo mite channel?\n");
+ retval = -EIO;
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ if (retval < 0)
+ return retval;
+#endif
+/*
+* XXX not sure what interrupt C group does
+* ni_writeb(dev, Interrupt_Group_C_Enable_Bit,
+* M_Offset_Interrupt_C_Enable); wait for dma to fill output fifo
+*/
+ for (i = 0; i < timeout; ++i) {
+ if (ni_readl(dev, M_Offset_CDIO_Status) & CDO_FIFO_Full_Bit)
+ break;
+ udelay(10);
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev, "dma failed to fill cdo fifo!\n");
+ s->cancel(dev, s);
+ return -EIO;
+ }
+ ni_writel(dev, CDO_Arm_Bit | CDO_Error_Interrupt_Enable_Set_Bit |
+ CDO_Empty_FIFO_Interrupt_Enable_Set_Bit,
+ M_Offset_CDIO_Command);
+ return retval;
+}
+
+static int ni_cdio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned cdo_mode_bits = CDO_FIFO_Mode_Bit | CDO_Halt_On_Error_Bit;
+ int retval;
+
+ ni_writel(dev, CDO_Reset_Bit, M_Offset_CDIO_Command);
+ switch (cmd->scan_begin_src) {
+ case TRIG_EXT:
+ cdo_mode_bits |=
+ CR_CHAN(cmd->scan_begin_arg) &
+ CDO_Sample_Source_Select_Mask;
+ break;
+ default:
+ BUG();
+ break;
+ }
+ if (cmd->scan_begin_arg & CR_INVERT)
+ cdo_mode_bits |= CDO_Polarity_Bit;
+ ni_writel(dev, cdo_mode_bits, M_Offset_CDO_Mode);
+ if (s->io_bits) {
+ ni_writel(dev, s->state, M_Offset_CDO_FIFO_Data);
+ ni_writel(dev, CDO_SW_Update_Bit, M_Offset_CDIO_Command);
+ ni_writel(dev, s->io_bits, M_Offset_CDO_Mask_Enable);
+ } else {
+ dev_err(dev->class_dev,
+ "attempted to run digital output command with no lines configured as outputs\n");
+ return -EIO;
+ }
+ retval = ni_request_cdo_mite_channel(dev);
+ if (retval < 0)
+ return retval;
+
+ s->async->inttrig = ni_cdo_inttrig;
+
+ return 0;
+}
+
+static int ni_cdio_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ ni_writel(dev, CDO_Disarm_Bit | CDO_Error_Interrupt_Enable_Clear_Bit |
+ CDO_Empty_FIFO_Interrupt_Enable_Clear_Bit |
+ CDO_FIFO_Request_Interrupt_Enable_Clear_Bit,
+ M_Offset_CDIO_Command);
+/*
+* XXX not sure what interrupt C group does ni_writeb(dev, 0,
+* M_Offset_Interrupt_C_Enable);
+*/
+ ni_writel(dev, 0, M_Offset_CDO_Mask_Enable);
+ ni_release_cdo_mite_channel(dev);
+ return 0;
+}
+
+static void handle_cdio_interrupt(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned cdio_status;
+ struct comedi_subdevice *s = &dev->subdevices[NI_DIO_SUBDEV];
+#ifdef PCIDMA
+ unsigned long flags;
+#endif
+
+ if (!devpriv->is_m_series)
+ return;
+#ifdef PCIDMA
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->cdo_mite_chan) {
+ unsigned cdo_mite_status =
+ mite_get_status(devpriv->cdo_mite_chan);
+ if (cdo_mite_status & CHSR_LINKC) {
+ writel(CHOR_CLRLC,
+ devpriv->mite->mite_io_addr +
+ MITE_CHOR(devpriv->cdo_mite_chan->channel));
+ }
+ mite_sync_output_dma(devpriv->cdo_mite_chan, s);
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+#endif
+
+ cdio_status = ni_readl(dev, M_Offset_CDIO_Status);
+ if (cdio_status & (CDO_Overrun_Bit | CDO_Underflow_Bit)) {
+ /* XXX just guessing this is needed and does something useful */
+ ni_writel(dev, CDO_Error_Interrupt_Confirm_Bit,
+ M_Offset_CDIO_Command);
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+ if (cdio_status & CDO_FIFO_Empty_Bit) {
+ ni_writel(dev, CDO_Empty_FIFO_Interrupt_Enable_Clear_Bit,
+ M_Offset_CDIO_Command);
+ /* s->async->events |= COMEDI_CB_EOA; */
+ }
+ comedi_handle_events(dev, s);
+}
+
+static int ni_serial_hw_readwrite8(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned char data_out,
+ unsigned char *data_in)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int status1;
+ int err = 0, count = 20;
+
+ devpriv->dio_output &= ~DIO_Serial_Data_Mask;
+ devpriv->dio_output |= DIO_Serial_Data_Out(data_out);
+ ni_stc_writew(dev, devpriv->dio_output, DIO_Output_Register);
+
+ status1 = ni_stc_readw(dev, Joint_Status_1_Register);
+ if (status1 & DIO_Serial_IO_In_Progress_St) {
+ err = -EBUSY;
+ goto Error;
+ }
+
+ devpriv->dio_control |= DIO_HW_Serial_Start;
+ ni_stc_writew(dev, devpriv->dio_control, DIO_Control_Register);
+ devpriv->dio_control &= ~DIO_HW_Serial_Start;
+
+ /* Wait until STC says we're done, but don't loop infinitely. */
+ while ((status1 = ni_stc_readw(dev, Joint_Status_1_Register)) &
+ DIO_Serial_IO_In_Progress_St) {
+ /* Delay one bit per loop */
+ udelay((devpriv->serial_interval_ns + 999) / 1000);
+ if (--count < 0) {
+ dev_err(dev->class_dev,
+ "SPI serial I/O didn't finish in time!\n");
+ err = -ETIME;
+ goto Error;
+ }
+ }
+
+ /* Delay for last bit. This delay is absolutely necessary, because
+ DIO_Serial_IO_In_Progress_St goes high one bit too early. */
+ udelay((devpriv->serial_interval_ns + 999) / 1000);
+
+ if (data_in)
+ *data_in = ni_stc_readw(dev, DIO_Serial_Input_Register);
+
+Error:
+ ni_stc_writew(dev, devpriv->dio_control, DIO_Control_Register);
+
+ return err;
+}
+
+static int ni_serial_sw_readwrite8(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned char data_out,
+ unsigned char *data_in)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned char mask, input = 0;
+
+ /* Wait for one bit before transfer */
+ udelay((devpriv->serial_interval_ns + 999) / 1000);
+
+ for (mask = 0x80; mask; mask >>= 1) {
+ /* Output current bit; note that we cannot touch s->state
+ because it is a per-subdevice field, and serial is
+ a separate subdevice from DIO. */
+ devpriv->dio_output &= ~DIO_SDOUT;
+ if (data_out & mask)
+ devpriv->dio_output |= DIO_SDOUT;
+ ni_stc_writew(dev, devpriv->dio_output, DIO_Output_Register);
+
+ /* Assert SDCLK (active low, inverted), wait for half of
+ the delay, deassert SDCLK, and wait for the other half. */
+ devpriv->dio_control |= DIO_Software_Serial_Control;
+ ni_stc_writew(dev, devpriv->dio_control, DIO_Control_Register);
+
+ udelay((devpriv->serial_interval_ns + 999) / 2000);
+
+ devpriv->dio_control &= ~DIO_Software_Serial_Control;
+ ni_stc_writew(dev, devpriv->dio_control, DIO_Control_Register);
+
+ udelay((devpriv->serial_interval_ns + 999) / 2000);
+
+ /* Input current bit */
+ if (ni_stc_readw(dev, DIO_Parallel_Input_Register) & DIO_SDIN)
+ input |= mask;
+ }
+
+ if (data_in)
+ *data_in = input;
+
+ return 0;
+}
+
+static int ni_serial_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ int err = insn->n;
+ unsigned char byte_out, byte_in = 0;
+
+ if (insn->n != 2)
+ return -EINVAL;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SERIAL_CLOCK:
+ devpriv->serial_hw_mode = 1;
+ devpriv->dio_control |= DIO_HW_Serial_Enable;
+
+ if (data[1] == SERIAL_DISABLED) {
+ devpriv->serial_hw_mode = 0;
+ devpriv->dio_control &= ~(DIO_HW_Serial_Enable |
+ DIO_Software_Serial_Control);
+ data[1] = SERIAL_DISABLED;
+ devpriv->serial_interval_ns = data[1];
+ } else if (data[1] <= SERIAL_600NS) {
+ /* Warning: this clock speed is too fast to reliably
+ control SCXI. */
+ devpriv->dio_control &= ~DIO_HW_Serial_Timebase;
+ devpriv->clock_and_fout |= Slow_Internal_Timebase;
+ devpriv->clock_and_fout &= ~DIO_Serial_Out_Divide_By_2;
+ data[1] = SERIAL_600NS;
+ devpriv->serial_interval_ns = data[1];
+ } else if (data[1] <= SERIAL_1_2US) {
+ devpriv->dio_control &= ~DIO_HW_Serial_Timebase;
+ devpriv->clock_and_fout |= Slow_Internal_Timebase |
+ DIO_Serial_Out_Divide_By_2;
+ data[1] = SERIAL_1_2US;
+ devpriv->serial_interval_ns = data[1];
+ } else if (data[1] <= SERIAL_10US) {
+ devpriv->dio_control |= DIO_HW_Serial_Timebase;
+ devpriv->clock_and_fout |= Slow_Internal_Timebase |
+ DIO_Serial_Out_Divide_By_2;
+ /* Note: DIO_Serial_Out_Divide_By_2 only affects
+ 600ns/1.2us. If you turn divide_by_2 off with the
+ slow clock, you will still get 10us, except then
+ all your delays are wrong. */
+ data[1] = SERIAL_10US;
+ devpriv->serial_interval_ns = data[1];
+ } else {
+ devpriv->dio_control &= ~(DIO_HW_Serial_Enable |
+ DIO_Software_Serial_Control);
+ devpriv->serial_hw_mode = 0;
+ data[1] = (data[1] / 1000) * 1000;
+ devpriv->serial_interval_ns = data[1];
+ }
+
+ ni_stc_writew(dev, devpriv->dio_control, DIO_Control_Register);
+ ni_stc_writew(dev, devpriv->clock_and_fout,
+ Clock_and_FOUT_Register);
+ return 1;
+
+ case INSN_CONFIG_BIDIRECTIONAL_DATA:
+
+ if (devpriv->serial_interval_ns == 0)
+ return -EINVAL;
+
+ byte_out = data[1] & 0xFF;
+
+ if (devpriv->serial_hw_mode) {
+ err = ni_serial_hw_readwrite8(dev, s, byte_out,
+ &byte_in);
+ } else if (devpriv->serial_interval_ns > 0) {
+ err = ni_serial_sw_readwrite8(dev, s, byte_out,
+ &byte_in);
+ } else {
+ dev_err(dev->class_dev, "serial disabled!\n");
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+ data[1] = byte_in & 0xFF;
+ return insn->n;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void init_ao_67xx(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ int i;
+
+ for (i = 0; i < s->n_chan; i++) {
+ ni_ao_win_outw(dev, AO_Channel(i) | 0x0,
+ AO_Configuration_2_67xx);
+ }
+ ni_ao_win_outw(dev, 0x0, AO_Later_Single_Point_Updates);
+}
+
+static unsigned ni_gpct_to_stc_register(enum ni_gpct_register reg)
+{
+ unsigned stc_register;
+
+ switch (reg) {
+ case NITIO_G0_AUTO_INC:
+ stc_register = G_Autoincrement_Register(0);
+ break;
+ case NITIO_G1_AUTO_INC:
+ stc_register = G_Autoincrement_Register(1);
+ break;
+ case NITIO_G0_CMD:
+ stc_register = G_Command_Register(0);
+ break;
+ case NITIO_G1_CMD:
+ stc_register = G_Command_Register(1);
+ break;
+ case NITIO_G0_HW_SAVE:
+ stc_register = G_HW_Save_Register(0);
+ break;
+ case NITIO_G1_HW_SAVE:
+ stc_register = G_HW_Save_Register(1);
+ break;
+ case NITIO_G0_SW_SAVE:
+ stc_register = G_Save_Register(0);
+ break;
+ case NITIO_G1_SW_SAVE:
+ stc_register = G_Save_Register(1);
+ break;
+ case NITIO_G0_MODE:
+ stc_register = G_Mode_Register(0);
+ break;
+ case NITIO_G1_MODE:
+ stc_register = G_Mode_Register(1);
+ break;
+ case NITIO_G0_LOADA:
+ stc_register = G_Load_A_Register(0);
+ break;
+ case NITIO_G1_LOADA:
+ stc_register = G_Load_A_Register(1);
+ break;
+ case NITIO_G0_LOADB:
+ stc_register = G_Load_B_Register(0);
+ break;
+ case NITIO_G1_LOADB:
+ stc_register = G_Load_B_Register(1);
+ break;
+ case NITIO_G0_INPUT_SEL:
+ stc_register = G_Input_Select_Register(0);
+ break;
+ case NITIO_G1_INPUT_SEL:
+ stc_register = G_Input_Select_Register(1);
+ break;
+ case NITIO_G01_STATUS:
+ stc_register = G_Status_Register;
+ break;
+ case NITIO_G01_RESET:
+ stc_register = Joint_Reset_Register;
+ break;
+ case NITIO_G01_STATUS1:
+ stc_register = Joint_Status_1_Register;
+ break;
+ case NITIO_G01_STATUS2:
+ stc_register = Joint_Status_2_Register;
+ break;
+ case NITIO_G0_INT_ACK:
+ stc_register = Interrupt_A_Ack_Register;
+ break;
+ case NITIO_G1_INT_ACK:
+ stc_register = Interrupt_B_Ack_Register;
+ break;
+ case NITIO_G0_STATUS:
+ stc_register = AI_Status_1_Register;
+ break;
+ case NITIO_G1_STATUS:
+ stc_register = AO_Status_1_Register;
+ break;
+ case NITIO_G0_INT_ENA:
+ stc_register = Interrupt_A_Enable_Register;
+ break;
+ case NITIO_G1_INT_ENA:
+ stc_register = Interrupt_B_Enable_Register;
+ break;
+ default:
+ pr_err("%s: unhandled register 0x%x in switch.\n",
+ __func__, reg);
+ BUG();
+ return 0;
+ }
+ return stc_register;
+}
+
+static void ni_gpct_write_register(struct ni_gpct *counter, unsigned bits,
+ enum ni_gpct_register reg)
+{
+ struct comedi_device *dev = counter->counter_dev->dev;
+ unsigned stc_register;
+ /* bits in the join reset register which are relevant to counters */
+ static const unsigned gpct_joint_reset_mask = G0_Reset | G1_Reset;
+ static const unsigned gpct_interrupt_a_enable_mask =
+ G0_Gate_Interrupt_Enable | G0_TC_Interrupt_Enable;
+ static const unsigned gpct_interrupt_b_enable_mask =
+ G1_Gate_Interrupt_Enable | G1_TC_Interrupt_Enable;
+
+ switch (reg) {
+ /* m-series-only registers */
+ case NITIO_G0_CNT_MODE:
+ ni_writew(dev, bits, M_Offset_G0_Counting_Mode);
+ break;
+ case NITIO_G1_CNT_MODE:
+ ni_writew(dev, bits, M_Offset_G1_Counting_Mode);
+ break;
+ case NITIO_G0_GATE2:
+ ni_writew(dev, bits, M_Offset_G0_Second_Gate);
+ break;
+ case NITIO_G1_GATE2:
+ ni_writew(dev, bits, M_Offset_G1_Second_Gate);
+ break;
+ case NITIO_G0_DMA_CFG:
+ ni_writew(dev, bits, M_Offset_G0_DMA_Config);
+ break;
+ case NITIO_G1_DMA_CFG:
+ ni_writew(dev, bits, M_Offset_G1_DMA_Config);
+ break;
+ case NITIO_G0_ABZ:
+ ni_writew(dev, bits, M_Offset_G0_MSeries_ABZ);
+ break;
+ case NITIO_G1_ABZ:
+ ni_writew(dev, bits, M_Offset_G1_MSeries_ABZ);
+ break;
+
+ /* 32 bit registers */
+ case NITIO_G0_LOADA:
+ case NITIO_G1_LOADA:
+ case NITIO_G0_LOADB:
+ case NITIO_G1_LOADB:
+ stc_register = ni_gpct_to_stc_register(reg);
+ ni_stc_writel(dev, bits, stc_register);
+ break;
+
+ /* 16 bit registers */
+ case NITIO_G0_INT_ENA:
+ BUG_ON(bits & ~gpct_interrupt_a_enable_mask);
+ ni_set_bitfield(dev, Interrupt_A_Enable_Register,
+ gpct_interrupt_a_enable_mask, bits);
+ break;
+ case NITIO_G1_INT_ENA:
+ BUG_ON(bits & ~gpct_interrupt_b_enable_mask);
+ ni_set_bitfield(dev, Interrupt_B_Enable_Register,
+ gpct_interrupt_b_enable_mask, bits);
+ break;
+ case NITIO_G01_RESET:
+ BUG_ON(bits & ~gpct_joint_reset_mask);
+ /* fall-through */
+ default:
+ stc_register = ni_gpct_to_stc_register(reg);
+ ni_stc_writew(dev, bits, stc_register);
+ }
+}
+
+static unsigned ni_gpct_read_register(struct ni_gpct *counter,
+ enum ni_gpct_register reg)
+{
+ struct comedi_device *dev = counter->counter_dev->dev;
+ unsigned stc_register;
+
+ switch (reg) {
+ /* m-series only registers */
+ case NITIO_G0_DMA_STATUS:
+ return ni_readw(dev, M_Offset_G0_DMA_Status);
+ case NITIO_G1_DMA_STATUS:
+ return ni_readw(dev, M_Offset_G1_DMA_Status);
+
+ /* 32 bit registers */
+ case NITIO_G0_HW_SAVE:
+ case NITIO_G1_HW_SAVE:
+ case NITIO_G0_SW_SAVE:
+ case NITIO_G1_SW_SAVE:
+ stc_register = ni_gpct_to_stc_register(reg);
+ return ni_stc_readl(dev, stc_register);
+
+ /* 16 bit registers */
+ default:
+ stc_register = ni_gpct_to_stc_register(reg);
+ return ni_stc_readw(dev, stc_register);
+ }
+ return 0;
+}
+
+static int ni_freq_out_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int val = devpriv->clock_and_fout & FOUT_Divider_mask;
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = val;
+
+ return insn->n;
+}
+
+static int ni_freq_out_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (insn->n) {
+ devpriv->clock_and_fout &= ~FOUT_Enable;
+ ni_stc_writew(dev, devpriv->clock_and_fout,
+ Clock_and_FOUT_Register);
+ devpriv->clock_and_fout &= ~FOUT_Divider_mask;
+
+ /* use the last data value to set the fout divider */
+ devpriv->clock_and_fout |= FOUT_Divider(data[insn->n - 1]);
+
+ devpriv->clock_and_fout |= FOUT_Enable;
+ ni_stc_writew(dev, devpriv->clock_and_fout,
+ Clock_and_FOUT_Register);
+ }
+ return insn->n;
+}
+
+static int ni_freq_out_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ switch (data[1]) {
+ case NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC:
+ devpriv->clock_and_fout &= ~FOUT_Timebase_Select;
+ break;
+ case NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC:
+ devpriv->clock_and_fout |= FOUT_Timebase_Select;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ni_stc_writew(dev, devpriv->clock_and_fout,
+ Clock_and_FOUT_Register);
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ if (devpriv->clock_and_fout & FOUT_Timebase_Select) {
+ data[1] = NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC;
+ data[2] = TIMEBASE_2_NS;
+ } else {
+ data[1] = NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC;
+ data[2] = TIMEBASE_1_NS * 2;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return insn->n;
+}
+
+static int ni_8255_callback(struct comedi_device *dev,
+ int dir, int port, int data, unsigned long iobase)
+{
+ if (dir) {
+ ni_writeb(dev, data, iobase + 2 * port);
+ return 0;
+ }
+
+ return ni_readb(dev, iobase + 2 * port);
+}
+
+static int ni_get_pwm_config(struct comedi_device *dev, unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ data[1] = devpriv->pwm_up_count * devpriv->clock_ns;
+ data[2] = devpriv->pwm_down_count * devpriv->clock_ns;
+ return 3;
+}
+
+static int ni_m_series_pwm_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned up_count, down_count;
+
+ switch (data[0]) {
+ case INSN_CONFIG_PWM_OUTPUT:
+ switch (data[1]) {
+ case CMDF_ROUND_NEAREST:
+ up_count =
+ (data[2] +
+ devpriv->clock_ns / 2) / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_DOWN:
+ up_count = data[2] / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_UP:
+ up_count =
+ (data[2] + devpriv->clock_ns -
+ 1) / devpriv->clock_ns;
+ break;
+ default:
+ return -EINVAL;
+ }
+ switch (data[3]) {
+ case CMDF_ROUND_NEAREST:
+ down_count =
+ (data[4] +
+ devpriv->clock_ns / 2) / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_DOWN:
+ down_count = data[4] / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_UP:
+ down_count =
+ (data[4] + devpriv->clock_ns -
+ 1) / devpriv->clock_ns;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (up_count * devpriv->clock_ns != data[2] ||
+ down_count * devpriv->clock_ns != data[4]) {
+ data[2] = up_count * devpriv->clock_ns;
+ data[4] = down_count * devpriv->clock_ns;
+ return -EAGAIN;
+ }
+ ni_writel(dev, MSeries_Cal_PWM_High_Time_Bits(up_count) |
+ MSeries_Cal_PWM_Low_Time_Bits(down_count),
+ M_Offset_Cal_PWM);
+ devpriv->pwm_up_count = up_count;
+ devpriv->pwm_down_count = down_count;
+ return 5;
+ case INSN_CONFIG_GET_PWM_OUTPUT:
+ return ni_get_pwm_config(dev, data);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ni_6143_pwm_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned up_count, down_count;
+
+ switch (data[0]) {
+ case INSN_CONFIG_PWM_OUTPUT:
+ switch (data[1]) {
+ case CMDF_ROUND_NEAREST:
+ up_count =
+ (data[2] +
+ devpriv->clock_ns / 2) / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_DOWN:
+ up_count = data[2] / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_UP:
+ up_count =
+ (data[2] + devpriv->clock_ns -
+ 1) / devpriv->clock_ns;
+ break;
+ default:
+ return -EINVAL;
+ }
+ switch (data[3]) {
+ case CMDF_ROUND_NEAREST:
+ down_count =
+ (data[4] +
+ devpriv->clock_ns / 2) / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_DOWN:
+ down_count = data[4] / devpriv->clock_ns;
+ break;
+ case CMDF_ROUND_UP:
+ down_count =
+ (data[4] + devpriv->clock_ns -
+ 1) / devpriv->clock_ns;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (up_count * devpriv->clock_ns != data[2] ||
+ down_count * devpriv->clock_ns != data[4]) {
+ data[2] = up_count * devpriv->clock_ns;
+ data[4] = down_count * devpriv->clock_ns;
+ return -EAGAIN;
+ }
+ ni_writel(dev, up_count, Calibration_HighTime_6143);
+ devpriv->pwm_up_count = up_count;
+ ni_writel(dev, down_count, Calibration_LowTime_6143);
+ devpriv->pwm_down_count = down_count;
+ return 5;
+ case INSN_CONFIG_GET_PWM_OUTPUT:
+ return ni_get_pwm_config(dev, data);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pack_mb88341(int addr, int val, int *bitstring)
+{
+ /*
+ Fujitsu MB 88341
+ Note that address bits are reversed. Thanks to
+ Ingo Keen for noticing this.
+
+ Note also that the 88341 expects address values from
+ 1-12, whereas we use channel numbers 0-11. The NI
+ docs use 1-12, also, so be careful here.
+ */
+ addr++;
+ *bitstring = ((addr & 0x1) << 11) |
+ ((addr & 0x2) << 9) |
+ ((addr & 0x4) << 7) | ((addr & 0x8) << 5) | (val & 0xff);
+ return 12;
+}
+
+static int pack_dac8800(int addr, int val, int *bitstring)
+{
+ *bitstring = ((addr & 0x7) << 8) | (val & 0xff);
+ return 11;
+}
+
+static int pack_dac8043(int addr, int val, int *bitstring)
+{
+ *bitstring = val & 0xfff;
+ return 12;
+}
+
+static int pack_ad8522(int addr, int val, int *bitstring)
+{
+ *bitstring = (val & 0xfff) | (addr ? 0xc000 : 0xa000);
+ return 16;
+}
+
+static int pack_ad8804(int addr, int val, int *bitstring)
+{
+ *bitstring = ((addr & 0xf) << 8) | (val & 0xff);
+ return 12;
+}
+
+static int pack_ad8842(int addr, int val, int *bitstring)
+{
+ *bitstring = ((addr + 1) << 8) | (val & 0xff);
+ return 12;
+}
+
+struct caldac_struct {
+ int n_chans;
+ int n_bits;
+ int (*packbits)(int, int, int *);
+};
+
+static struct caldac_struct caldacs[] = {
+ [mb88341] = {12, 8, pack_mb88341},
+ [dac8800] = {8, 8, pack_dac8800},
+ [dac8043] = {1, 12, pack_dac8043},
+ [ad8522] = {2, 12, pack_ad8522},
+ [ad8804] = {12, 8, pack_ad8804},
+ [ad8842] = {8, 8, pack_ad8842},
+ [ad8804_debug] = {16, 8, pack_ad8804},
+};
+
+static void ni_write_caldac(struct comedi_device *dev, int addr, int val)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ unsigned int loadbit = 0, bits = 0, bit, bitstring = 0;
+ int i;
+ int type;
+
+ if (devpriv->caldacs[addr] == val)
+ return;
+ devpriv->caldacs[addr] = val;
+
+ for (i = 0; i < 3; i++) {
+ type = board->caldac[i];
+ if (type == caldac_none)
+ break;
+ if (addr < caldacs[type].n_chans) {
+ bits = caldacs[type].packbits(addr, val, &bitstring);
+ loadbit = SerDacLd(i);
+ break;
+ }
+ addr -= caldacs[type].n_chans;
+ }
+
+ for (bit = 1 << (bits - 1); bit; bit >>= 1) {
+ ni_writeb(dev, ((bit & bitstring) ? 0x02 : 0), Serial_Command);
+ udelay(1);
+ ni_writeb(dev, 1 | ((bit & bitstring) ? 0x02 : 0),
+ Serial_Command);
+ udelay(1);
+ }
+ ni_writeb(dev, loadbit, Serial_Command);
+ udelay(1);
+ ni_writeb(dev, 0, Serial_Command);
+}
+
+static int ni_calib_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ ni_write_caldac(dev, CR_CHAN(insn->chanspec), data[0]);
+
+ return 1;
+}
+
+static int ni_calib_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ data[0] = devpriv->caldacs[CR_CHAN(insn->chanspec)];
+
+ return 1;
+}
+
+static void caldac_setup(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ int i, j;
+ int n_dacs;
+ int n_chans = 0;
+ int n_bits;
+ int diffbits = 0;
+ int type;
+ int chan;
+
+ type = board->caldac[0];
+ if (type == caldac_none)
+ return;
+ n_bits = caldacs[type].n_bits;
+ for (i = 0; i < 3; i++) {
+ type = board->caldac[i];
+ if (type == caldac_none)
+ break;
+ if (caldacs[type].n_bits != n_bits)
+ diffbits = 1;
+ n_chans += caldacs[type].n_chans;
+ }
+ n_dacs = i;
+ s->n_chan = n_chans;
+
+ if (diffbits) {
+ unsigned int *maxdata_list;
+
+ if (n_chans > MAX_N_CALDACS)
+ dev_err(dev->class_dev,
+ "BUG! MAX_N_CALDACS too small\n");
+ s->maxdata_list = maxdata_list = devpriv->caldac_maxdata_list;
+ chan = 0;
+ for (i = 0; i < n_dacs; i++) {
+ type = board->caldac[i];
+ for (j = 0; j < caldacs[type].n_chans; j++) {
+ maxdata_list[chan] =
+ (1 << caldacs[type].n_bits) - 1;
+ chan++;
+ }
+ }
+
+ for (chan = 0; chan < s->n_chan; chan++)
+ ni_write_caldac(dev, i, s->maxdata_list[i] / 2);
+ } else {
+ type = board->caldac[0];
+ s->maxdata = (1 << caldacs[type].n_bits) - 1;
+
+ for (chan = 0; chan < s->n_chan; chan++)
+ ni_write_caldac(dev, i, s->maxdata / 2);
+ }
+}
+
+static int ni_read_eeprom(struct comedi_device *dev, int addr)
+{
+ int bit;
+ int bitstring;
+
+ bitstring = 0x0300 | ((addr & 0x100) << 3) | (addr & 0xff);
+ ni_writeb(dev, 0x04, Serial_Command);
+ for (bit = 0x8000; bit; bit >>= 1) {
+ ni_writeb(dev, 0x04 | ((bit & bitstring) ? 0x02 : 0),
+ Serial_Command);
+ ni_writeb(dev, 0x05 | ((bit & bitstring) ? 0x02 : 0),
+ Serial_Command);
+ }
+ bitstring = 0;
+ for (bit = 0x80; bit; bit >>= 1) {
+ ni_writeb(dev, 0x04, Serial_Command);
+ ni_writeb(dev, 0x05, Serial_Command);
+ bitstring |= ((ni_readb(dev, XXX_Status) & PROMOUT) ? bit : 0);
+ }
+ ni_writeb(dev, 0x00, Serial_Command);
+
+ return bitstring;
+}
+
+static int ni_eeprom_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[0] = ni_read_eeprom(dev, CR_CHAN(insn->chanspec));
+
+ return 1;
+}
+
+static int ni_m_series_eeprom_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ data[0] = devpriv->eeprom_buffer[CR_CHAN(insn->chanspec)];
+
+ return 1;
+}
+
+static unsigned ni_old_get_pfi_routing(struct comedi_device *dev,
+ unsigned chan)
+{
+ /* pre-m-series boards have fixed signals on pfi pins */
+ switch (chan) {
+ case 0:
+ return NI_PFI_OUTPUT_AI_START1;
+ case 1:
+ return NI_PFI_OUTPUT_AI_START2;
+ case 2:
+ return NI_PFI_OUTPUT_AI_CONVERT;
+ case 3:
+ return NI_PFI_OUTPUT_G_SRC1;
+ case 4:
+ return NI_PFI_OUTPUT_G_GATE1;
+ case 5:
+ return NI_PFI_OUTPUT_AO_UPDATE_N;
+ case 6:
+ return NI_PFI_OUTPUT_AO_START1;
+ case 7:
+ return NI_PFI_OUTPUT_AI_START_PULSE;
+ case 8:
+ return NI_PFI_OUTPUT_G_SRC0;
+ case 9:
+ return NI_PFI_OUTPUT_G_GATE0;
+ default:
+ dev_err(dev->class_dev, "bug, unhandled case in switch.\n");
+ break;
+ }
+ return 0;
+}
+
+static int ni_old_set_pfi_routing(struct comedi_device *dev,
+ unsigned chan, unsigned source)
+{
+ /* pre-m-series boards have fixed signals on pfi pins */
+ if (source != ni_old_get_pfi_routing(dev, chan))
+ return -EINVAL;
+ return 2;
+}
+
+static unsigned ni_m_series_get_pfi_routing(struct comedi_device *dev,
+ unsigned chan)
+{
+ struct ni_private *devpriv = dev->private;
+ const unsigned array_offset = chan / 3;
+
+ return MSeries_PFI_Output_Select_Source(chan,
+ devpriv->pfi_output_select_reg[array_offset]);
+}
+
+static int ni_m_series_set_pfi_routing(struct comedi_device *dev,
+ unsigned chan, unsigned source)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned pfi_reg_index;
+ unsigned array_offset;
+
+ if ((source & 0x1f) != source)
+ return -EINVAL;
+ pfi_reg_index = 1 + chan / 3;
+ array_offset = pfi_reg_index - 1;
+ devpriv->pfi_output_select_reg[array_offset] &=
+ ~MSeries_PFI_Output_Select_Mask(chan);
+ devpriv->pfi_output_select_reg[array_offset] |=
+ MSeries_PFI_Output_Select_Bits(chan, source);
+ ni_writew(dev, devpriv->pfi_output_select_reg[array_offset],
+ M_Offset_PFI_Output_Select(pfi_reg_index));
+ return 2;
+}
+
+static unsigned ni_get_pfi_routing(struct comedi_device *dev, unsigned chan)
+{
+ struct ni_private *devpriv = dev->private;
+
+ return (devpriv->is_m_series)
+ ? ni_m_series_get_pfi_routing(dev, chan)
+ : ni_old_get_pfi_routing(dev, chan);
+}
+
+static int ni_set_pfi_routing(struct comedi_device *dev, unsigned chan,
+ unsigned source)
+{
+ struct ni_private *devpriv = dev->private;
+
+ return (devpriv->is_m_series)
+ ? ni_m_series_set_pfi_routing(dev, chan, source)
+ : ni_old_set_pfi_routing(dev, chan, source);
+}
+
+static int ni_config_filter(struct comedi_device *dev,
+ unsigned pfi_channel,
+ enum ni_pfi_filter_select filter)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned bits;
+
+ if (!devpriv->is_m_series)
+ return -ENOTSUPP;
+
+ bits = ni_readl(dev, M_Offset_PFI_Filter);
+ bits &= ~MSeries_PFI_Filter_Select_Mask(pfi_channel);
+ bits |= MSeries_PFI_Filter_Select_Bits(pfi_channel, filter);
+ ni_writel(dev, bits, M_Offset_PFI_Filter);
+ return 0;
+}
+
+static int ni_pfi_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int chan;
+
+ if (insn->n < 1)
+ return -EINVAL;
+
+ chan = CR_CHAN(insn->chanspec);
+
+ switch (data[0]) {
+ case COMEDI_OUTPUT:
+ ni_set_bits(dev, IO_Bidirection_Pin_Register, 1 << chan, 1);
+ break;
+ case COMEDI_INPUT:
+ ni_set_bits(dev, IO_Bidirection_Pin_Register, 1 << chan, 0);
+ break;
+ case INSN_CONFIG_DIO_QUERY:
+ data[1] =
+ (devpriv->io_bidirection_pin_reg & (1 << chan)) ?
+ COMEDI_OUTPUT : COMEDI_INPUT;
+ return 0;
+ case INSN_CONFIG_SET_ROUTING:
+ return ni_set_pfi_routing(dev, chan, data[1]);
+ case INSN_CONFIG_GET_ROUTING:
+ data[1] = ni_get_pfi_routing(dev, chan);
+ break;
+ case INSN_CONFIG_FILTER:
+ return ni_config_filter(dev, chan, data[1]);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ni_pfi_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (!devpriv->is_m_series)
+ return -ENOTSUPP;
+
+ if (comedi_dio_update_state(s, data))
+ ni_writew(dev, s->state, M_Offset_PFI_DO);
+
+ data[1] = ni_readw(dev, M_Offset_PFI_DI);
+
+ return insn->n;
+}
+
+static int cs5529_wait_for_idle(struct comedi_device *dev)
+{
+ unsigned short status;
+ const int timeout = HZ;
+ int i;
+
+ for (i = 0; i < timeout; i++) {
+ status = ni_ao_win_inw(dev, CAL_ADC_Status_67xx);
+ if ((status & CSS_ADC_BUSY) == 0)
+ break;
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (schedule_timeout(1))
+ return -EIO;
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev, "timeout\n");
+ return -ETIME;
+ }
+ return 0;
+}
+
+static void cs5529_command(struct comedi_device *dev, unsigned short value)
+{
+ static const int timeout = 100;
+ int i;
+
+ ni_ao_win_outw(dev, value, CAL_ADC_Command_67xx);
+ /* give time for command to start being serially clocked into cs5529.
+ * this insures that the CSS_ADC_BUSY bit will get properly
+ * set before we exit this function.
+ */
+ for (i = 0; i < timeout; i++) {
+ if ((ni_ao_win_inw(dev, CAL_ADC_Status_67xx) & CSS_ADC_BUSY))
+ break;
+ udelay(1);
+ }
+ if (i == timeout)
+ dev_err(dev->class_dev,
+ "possible problem - never saw adc go busy?\n");
+}
+
+static int cs5529_do_conversion(struct comedi_device *dev,
+ unsigned short *data)
+{
+ int retval;
+ unsigned short status;
+
+ cs5529_command(dev, CSCMD_COMMAND | CSCMD_SINGLE_CONVERSION);
+ retval = cs5529_wait_for_idle(dev);
+ if (retval) {
+ dev_err(dev->class_dev,
+ "timeout or signal in cs5529_do_conversion()\n");
+ return -ETIME;
+ }
+ status = ni_ao_win_inw(dev, CAL_ADC_Status_67xx);
+ if (status & CSS_OSC_DETECT) {
+ dev_err(dev->class_dev,
+ "cs5529 conversion error, status CSS_OSC_DETECT\n");
+ return -EIO;
+ }
+ if (status & CSS_OVERRANGE) {
+ dev_err(dev->class_dev,
+ "cs5529 conversion error, overrange (ignoring)\n");
+ }
+ if (data) {
+ *data = ni_ao_win_inw(dev, CAL_ADC_Data_67xx);
+ /* cs5529 returns 16 bit signed data in bipolar mode */
+ *data ^= (1 << 15);
+ }
+ return 0;
+}
+
+static int cs5529_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int n, retval;
+ unsigned short sample;
+ unsigned int channel_select;
+ const unsigned int INTERNAL_REF = 0x1000;
+
+ /* Set calibration adc source. Docs lie, reference select bits 8 to 11
+ * do nothing. bit 12 seems to chooses internal reference voltage, bit
+ * 13 causes the adc input to go overrange (maybe reads external reference?) */
+ if (insn->chanspec & CR_ALT_SOURCE)
+ channel_select = INTERNAL_REF;
+ else
+ channel_select = CR_CHAN(insn->chanspec);
+ ni_ao_win_outw(dev, channel_select, AO_Calibration_Channel_Select_67xx);
+
+ for (n = 0; n < insn->n; n++) {
+ retval = cs5529_do_conversion(dev, &sample);
+ if (retval < 0)
+ return retval;
+ data[n] = sample;
+ }
+ return insn->n;
+}
+
+static void cs5529_config_write(struct comedi_device *dev, unsigned int value,
+ unsigned int reg_select_bits)
+{
+ ni_ao_win_outw(dev, ((value >> 16) & 0xff),
+ CAL_ADC_Config_Data_High_Word_67xx);
+ ni_ao_win_outw(dev, (value & 0xffff),
+ CAL_ADC_Config_Data_Low_Word_67xx);
+ reg_select_bits &= CSCMD_REGISTER_SELECT_MASK;
+ cs5529_command(dev, CSCMD_COMMAND | reg_select_bits);
+ if (cs5529_wait_for_idle(dev))
+ dev_err(dev->class_dev,
+ "timeout or signal in %s\n", __func__);
+}
+
+static int init_cs5529(struct comedi_device *dev)
+{
+ unsigned int config_bits =
+ CSCFG_PORT_MODE | CSCFG_WORD_RATE_2180_CYCLES;
+
+#if 1
+ /* do self-calibration */
+ cs5529_config_write(dev, config_bits | CSCFG_SELF_CAL_OFFSET_GAIN,
+ CSCMD_CONFIG_REGISTER);
+ /* need to force a conversion for calibration to run */
+ cs5529_do_conversion(dev, NULL);
+#else
+ /* force gain calibration to 1 */
+ cs5529_config_write(dev, 0x400000, CSCMD_GAIN_REGISTER);
+ cs5529_config_write(dev, config_bits | CSCFG_SELF_CAL_OFFSET,
+ CSCMD_CONFIG_REGISTER);
+ if (cs5529_wait_for_idle(dev))
+ dev_err(dev->class_dev,
+ "timeout or signal in %s\n", __func__);
+#endif
+ return 0;
+}
+
+/*
+ * Find best multiplier/divider to try and get the PLL running at 80 MHz
+ * given an arbitrary frequency input clock.
+ */
+static int ni_mseries_get_pll_parameters(unsigned reference_period_ns,
+ unsigned *freq_divider,
+ unsigned *freq_multiplier,
+ unsigned *actual_period_ns)
+{
+ unsigned div;
+ unsigned best_div = 1;
+ static const unsigned max_div = 0x10;
+ unsigned mult;
+ unsigned best_mult = 1;
+ static const unsigned max_mult = 0x100;
+ static const unsigned pico_per_nano = 1000;
+
+ const unsigned reference_picosec = reference_period_ns * pico_per_nano;
+ /* m-series wants the phased-locked loop to output 80MHz, which is divided by 4 to
+ * 20 MHz for most timing clocks */
+ static const unsigned target_picosec = 12500;
+ static const unsigned fudge_factor_80_to_20Mhz = 4;
+ int best_period_picosec = 0;
+
+ for (div = 1; div <= max_div; ++div) {
+ for (mult = 1; mult <= max_mult; ++mult) {
+ unsigned new_period_ps =
+ (reference_picosec * div) / mult;
+ if (abs(new_period_ps - target_picosec) <
+ abs(best_period_picosec - target_picosec)) {
+ best_period_picosec = new_period_ps;
+ best_div = div;
+ best_mult = mult;
+ }
+ }
+ }
+ if (best_period_picosec == 0)
+ return -EIO;
+
+ *freq_divider = best_div;
+ *freq_multiplier = best_mult;
+ *actual_period_ns =
+ (best_period_picosec * fudge_factor_80_to_20Mhz +
+ (pico_per_nano / 2)) / pico_per_nano;
+ return 0;
+}
+
+static int ni_mseries_set_pll_master_clock(struct comedi_device *dev,
+ unsigned source, unsigned period_ns)
+{
+ struct ni_private *devpriv = dev->private;
+ static const unsigned min_period_ns = 50;
+ static const unsigned max_period_ns = 1000;
+ static const unsigned timeout = 1000;
+ unsigned pll_control_bits;
+ unsigned freq_divider;
+ unsigned freq_multiplier;
+ unsigned i;
+ int retval;
+
+ if (source == NI_MIO_PLL_PXI10_CLOCK)
+ period_ns = 100;
+ /* these limits are somewhat arbitrary, but NI advertises 1 to 20MHz range so we'll use that */
+ if (period_ns < min_period_ns || period_ns > max_period_ns) {
+ dev_err(dev->class_dev,
+ "%s: you must specify an input clock frequency between %i and %i nanosec for the phased-lock loop\n",
+ __func__, min_period_ns, max_period_ns);
+ return -EINVAL;
+ }
+ devpriv->rtsi_trig_direction_reg &= ~Use_RTSI_Clock_Bit;
+ ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
+ RTSI_Trig_Direction_Register);
+ pll_control_bits =
+ MSeries_PLL_Enable_Bit | MSeries_PLL_VCO_Mode_75_150MHz_Bits;
+ devpriv->clock_and_fout2 |=
+ MSeries_Timebase1_Select_Bit | MSeries_Timebase3_Select_Bit;
+ devpriv->clock_and_fout2 &= ~MSeries_PLL_In_Source_Select_Mask;
+ switch (source) {
+ case NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK:
+ devpriv->clock_and_fout2 |=
+ MSeries_PLL_In_Source_Select_Star_Trigger_Bits;
+ break;
+ case NI_MIO_PLL_PXI10_CLOCK:
+ /* pxi clock is 10MHz */
+ devpriv->clock_and_fout2 |=
+ MSeries_PLL_In_Source_Select_PXI_Clock10;
+ break;
+ default:
+ {
+ unsigned rtsi_channel;
+ static const unsigned max_rtsi_channel = 7;
+
+ for (rtsi_channel = 0; rtsi_channel <= max_rtsi_channel;
+ ++rtsi_channel) {
+ if (source ==
+ NI_MIO_PLL_RTSI_CLOCK(rtsi_channel)) {
+ devpriv->clock_and_fout2 |=
+ MSeries_PLL_In_Source_Select_RTSI_Bits
+ (rtsi_channel);
+ break;
+ }
+ }
+ if (rtsi_channel > max_rtsi_channel)
+ return -EINVAL;
+ }
+ break;
+ }
+ retval = ni_mseries_get_pll_parameters(period_ns,
+ &freq_divider,
+ &freq_multiplier,
+ &devpriv->clock_ns);
+ if (retval < 0) {
+ dev_err(dev->class_dev,
+ "bug, failed to find pll parameters\n");
+ return retval;
+ }
+
+ ni_writew(dev, devpriv->clock_and_fout2, M_Offset_Clock_and_Fout2);
+ pll_control_bits |=
+ MSeries_PLL_Divisor_Bits(freq_divider) |
+ MSeries_PLL_Multiplier_Bits(freq_multiplier);
+
+ ni_writew(dev, pll_control_bits, M_Offset_PLL_Control);
+ devpriv->clock_source = source;
+ /* it seems to typically take a few hundred microseconds for PLL to lock */
+ for (i = 0; i < timeout; ++i) {
+ if (ni_readw(dev, M_Offset_PLL_Status) & MSeries_PLL_Locked_Bit)
+ break;
+ udelay(1);
+ }
+ if (i == timeout) {
+ dev_err(dev->class_dev,
+ "%s: timed out waiting for PLL to lock to reference clock source %i with period %i ns\n",
+ __func__, source, period_ns);
+ return -ETIMEDOUT;
+ }
+ return 3;
+}
+
+static int ni_set_master_clock(struct comedi_device *dev,
+ unsigned source, unsigned period_ns)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (source == NI_MIO_INTERNAL_CLOCK) {
+ devpriv->rtsi_trig_direction_reg &= ~Use_RTSI_Clock_Bit;
+ ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
+ RTSI_Trig_Direction_Register);
+ devpriv->clock_ns = TIMEBASE_1_NS;
+ if (devpriv->is_m_series) {
+ devpriv->clock_and_fout2 &=
+ ~(MSeries_Timebase1_Select_Bit |
+ MSeries_Timebase3_Select_Bit);
+ ni_writew(dev, devpriv->clock_and_fout2,
+ M_Offset_Clock_and_Fout2);
+ ni_writew(dev, 0, M_Offset_PLL_Control);
+ }
+ devpriv->clock_source = source;
+ } else {
+ if (devpriv->is_m_series) {
+ return ni_mseries_set_pll_master_clock(dev, source,
+ period_ns);
+ } else {
+ if (source == NI_MIO_RTSI_CLOCK) {
+ devpriv->rtsi_trig_direction_reg |=
+ Use_RTSI_Clock_Bit;
+ ni_stc_writew(dev,
+ devpriv->rtsi_trig_direction_reg,
+ RTSI_Trig_Direction_Register);
+ if (period_ns == 0) {
+ dev_err(dev->class_dev,
+ "we don't handle an unspecified clock period correctly yet, returning error\n");
+ return -EINVAL;
+ }
+ devpriv->clock_ns = period_ns;
+ devpriv->clock_source = source;
+ } else {
+ return -EINVAL;
+ }
+ }
+ }
+ return 3;
+}
+
+static unsigned num_configurable_rtsi_channels(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+
+ return (devpriv->is_m_series) ? 8 : 7;
+}
+
+static int ni_valid_rtsi_output_source(struct comedi_device *dev,
+ unsigned chan, unsigned source)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (chan >= num_configurable_rtsi_channels(dev)) {
+ if (chan == old_RTSI_clock_channel) {
+ if (source == NI_RTSI_OUTPUT_RTSI_OSC)
+ return 1;
+
+ dev_err(dev->class_dev,
+ "%s: invalid source for channel=%i, channel %i is always the RTSI clock for pre-m-series boards\n",
+ __func__, chan, old_RTSI_clock_channel);
+ return 0;
+ }
+ return 0;
+ }
+ switch (source) {
+ case NI_RTSI_OUTPUT_ADR_START1:
+ case NI_RTSI_OUTPUT_ADR_START2:
+ case NI_RTSI_OUTPUT_SCLKG:
+ case NI_RTSI_OUTPUT_DACUPDN:
+ case NI_RTSI_OUTPUT_DA_START1:
+ case NI_RTSI_OUTPUT_G_SRC0:
+ case NI_RTSI_OUTPUT_G_GATE0:
+ case NI_RTSI_OUTPUT_RGOUT0:
+ case NI_RTSI_OUTPUT_RTSI_BRD_0:
+ return 1;
+ case NI_RTSI_OUTPUT_RTSI_OSC:
+ return (devpriv->is_m_series) ? 1 : 0;
+ default:
+ return 0;
+ }
+}
+
+static int ni_set_rtsi_routing(struct comedi_device *dev,
+ unsigned chan, unsigned source)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (ni_valid_rtsi_output_source(dev, chan, source) == 0)
+ return -EINVAL;
+ if (chan < 4) {
+ devpriv->rtsi_trig_a_output_reg &= ~RTSI_Trig_Output_Mask(chan);
+ devpriv->rtsi_trig_a_output_reg |=
+ RTSI_Trig_Output_Bits(chan, source);
+ ni_stc_writew(dev, devpriv->rtsi_trig_a_output_reg,
+ RTSI_Trig_A_Output_Register);
+ } else if (chan < 8) {
+ devpriv->rtsi_trig_b_output_reg &= ~RTSI_Trig_Output_Mask(chan);
+ devpriv->rtsi_trig_b_output_reg |=
+ RTSI_Trig_Output_Bits(chan, source);
+ ni_stc_writew(dev, devpriv->rtsi_trig_b_output_reg,
+ RTSI_Trig_B_Output_Register);
+ }
+ return 2;
+}
+
+static unsigned ni_get_rtsi_routing(struct comedi_device *dev, unsigned chan)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (chan < 4) {
+ return RTSI_Trig_Output_Source(chan,
+ devpriv->rtsi_trig_a_output_reg);
+ } else if (chan < num_configurable_rtsi_channels(dev)) {
+ return RTSI_Trig_Output_Source(chan,
+ devpriv->rtsi_trig_b_output_reg);
+ } else {
+ if (chan == old_RTSI_clock_channel)
+ return NI_RTSI_OUTPUT_RTSI_OSC;
+ dev_err(dev->class_dev, "bug! should never get here?\n");
+ return 0;
+ }
+}
+
+static int ni_rtsi_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIO_OUTPUT:
+ if (chan < num_configurable_rtsi_channels(dev)) {
+ devpriv->rtsi_trig_direction_reg |=
+ RTSI_Output_Bit(chan, devpriv->is_m_series);
+ } else if (chan == old_RTSI_clock_channel) {
+ devpriv->rtsi_trig_direction_reg |=
+ Drive_RTSI_Clock_Bit;
+ }
+ ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
+ RTSI_Trig_Direction_Register);
+ break;
+ case INSN_CONFIG_DIO_INPUT:
+ if (chan < num_configurable_rtsi_channels(dev)) {
+ devpriv->rtsi_trig_direction_reg &=
+ ~RTSI_Output_Bit(chan, devpriv->is_m_series);
+ } else if (chan == old_RTSI_clock_channel) {
+ devpriv->rtsi_trig_direction_reg &=
+ ~Drive_RTSI_Clock_Bit;
+ }
+ ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
+ RTSI_Trig_Direction_Register);
+ break;
+ case INSN_CONFIG_DIO_QUERY:
+ if (chan < num_configurable_rtsi_channels(dev)) {
+ data[1] =
+ (devpriv->rtsi_trig_direction_reg &
+ RTSI_Output_Bit(chan, devpriv->is_m_series))
+ ? INSN_CONFIG_DIO_OUTPUT
+ : INSN_CONFIG_DIO_INPUT;
+ } else if (chan == old_RTSI_clock_channel) {
+ data[1] =
+ (devpriv->rtsi_trig_direction_reg &
+ Drive_RTSI_Clock_Bit)
+ ? INSN_CONFIG_DIO_OUTPUT : INSN_CONFIG_DIO_INPUT;
+ }
+ return 2;
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ return ni_set_master_clock(dev, data[1], data[2]);
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ data[1] = devpriv->clock_source;
+ data[2] = devpriv->clock_ns;
+ return 3;
+ case INSN_CONFIG_SET_ROUTING:
+ return ni_set_rtsi_routing(dev, chan, data[1]);
+ case INSN_CONFIG_GET_ROUTING:
+ data[1] = ni_get_rtsi_routing(dev, chan);
+ return 2;
+ default:
+ return -EINVAL;
+ }
+ return 1;
+}
+
+static int ni_rtsi_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = 0;
+
+ return insn->n;
+}
+
+static void ni_rtsi_init(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+
+ /* Initialises the RTSI bus signal switch to a default state */
+
+ /* Set clock mode to internal */
+ devpriv->clock_and_fout2 = MSeries_RTSI_10MHz_Bit;
+ if (ni_set_master_clock(dev, NI_MIO_INTERNAL_CLOCK, 0) < 0)
+ dev_err(dev->class_dev, "ni_set_master_clock failed, bug?\n");
+ /* default internal lines routing to RTSI bus lines */
+ devpriv->rtsi_trig_a_output_reg =
+ RTSI_Trig_Output_Bits(0,
+ NI_RTSI_OUTPUT_ADR_START1) |
+ RTSI_Trig_Output_Bits(1,
+ NI_RTSI_OUTPUT_ADR_START2) |
+ RTSI_Trig_Output_Bits(2,
+ NI_RTSI_OUTPUT_SCLKG) |
+ RTSI_Trig_Output_Bits(3, NI_RTSI_OUTPUT_DACUPDN);
+ ni_stc_writew(dev, devpriv->rtsi_trig_a_output_reg,
+ RTSI_Trig_A_Output_Register);
+ devpriv->rtsi_trig_b_output_reg =
+ RTSI_Trig_Output_Bits(4,
+ NI_RTSI_OUTPUT_DA_START1) |
+ RTSI_Trig_Output_Bits(5,
+ NI_RTSI_OUTPUT_G_SRC0) |
+ RTSI_Trig_Output_Bits(6, NI_RTSI_OUTPUT_G_GATE0);
+ if (devpriv->is_m_series)
+ devpriv->rtsi_trig_b_output_reg |=
+ RTSI_Trig_Output_Bits(7, NI_RTSI_OUTPUT_RTSI_OSC);
+ ni_stc_writew(dev, devpriv->rtsi_trig_b_output_reg,
+ RTSI_Trig_B_Output_Register);
+
+/*
+* Sets the source and direction of the 4 on board lines
+* ni_stc_writew(dev, 0x0000, RTSI_Board_Register);
+*/
+}
+
+#ifdef PCIDMA
+static int ni_gpct_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ int retval;
+
+ retval = ni_request_gpct_mite_channel(dev, counter->counter_index,
+ COMEDI_INPUT);
+ if (retval) {
+ dev_err(dev->class_dev,
+ "no dma channel available for use by counter\n");
+ return retval;
+ }
+ ni_tio_acknowledge(counter);
+ ni_e_series_enable_second_irq(dev, counter->counter_index, 1);
+
+ return ni_tio_cmd(dev, s);
+}
+
+static int ni_gpct_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ int retval;
+
+ retval = ni_tio_cancel(counter);
+ ni_e_series_enable_second_irq(dev, counter->counter_index, 0);
+ ni_release_gpct_mite_channel(dev, counter->counter_index);
+ return retval;
+}
+#endif
+
+#if 0
+/*
+ * Read the GPCTs current value.
+ */
+static int GPCT_G_Watch(struct comedi_device *dev, int chan)
+{
+ unsigned int hi1, hi2, lo;
+
+ devpriv->gpct_command[chan] &= ~G_Save_Trace;
+ ni_stc_writew(dev, devpriv->gpct_command[chan],
+ G_Command_Register(chan));
+
+ devpriv->gpct_command[chan] |= G_Save_Trace;
+ ni_stc_writew(dev, devpriv->gpct_command[chan],
+ G_Command_Register(chan));
+
+ /* This procedure is used because the two registers cannot
+ * be read atomically. */
+ do {
+ hi1 = ni_stc_readw(dev, G_Save_Register_High(chan));
+ lo = ni_stc_readw(dev, G_Save_Register_Low(chan));
+ hi2 = ni_stc_readw(dev, G_Save_Register_High(chan));
+ } while (hi1 != hi2);
+
+ return (hi1 << 16) | lo;
+}
+
+static void GPCT_Reset(struct comedi_device *dev, int chan)
+{
+ int temp_ack_reg = 0;
+
+ devpriv->gpct_cur_operation[chan] = GPCT_RESET;
+
+ switch (chan) {
+ case 0:
+ ni_stc_writew(dev, G0_Reset, Joint_Reset_Register);
+ ni_set_bits(dev, Interrupt_A_Enable_Register,
+ G0_TC_Interrupt_Enable, 0);
+ ni_set_bits(dev, Interrupt_A_Enable_Register,
+ G0_Gate_Interrupt_Enable, 0);
+ temp_ack_reg |= G0_Gate_Error_Confirm;
+ temp_ack_reg |= G0_TC_Error_Confirm;
+ temp_ack_reg |= G0_TC_Interrupt_Ack;
+ temp_ack_reg |= G0_Gate_Interrupt_Ack;
+ ni_stc_writew(dev, temp_ack_reg, Interrupt_A_Ack_Register);
+
+ /* problem...this interferes with the other ctr... */
+ devpriv->an_trig_etc_reg |= GPFO_0_Output_Enable;
+ ni_stc_writew(dev, devpriv->an_trig_etc_reg,
+ Analog_Trigger_Etc_Register);
+ break;
+ case 1:
+ ni_stc_writew(dev, G1_Reset, Joint_Reset_Register);
+ ni_set_bits(dev, Interrupt_B_Enable_Register,
+ G1_TC_Interrupt_Enable, 0);
+ ni_set_bits(dev, Interrupt_B_Enable_Register,
+ G0_Gate_Interrupt_Enable, 0);
+ temp_ack_reg |= G1_Gate_Error_Confirm;
+ temp_ack_reg |= G1_TC_Error_Confirm;
+ temp_ack_reg |= G1_TC_Interrupt_Ack;
+ temp_ack_reg |= G1_Gate_Interrupt_Ack;
+ ni_stc_writew(dev, temp_ack_reg, Interrupt_B_Ack_Register);
+
+ devpriv->an_trig_etc_reg |= GPFO_1_Output_Enable;
+ ni_stc_writew(dev, devpriv->an_trig_etc_reg,
+ Analog_Trigger_Etc_Register);
+ break;
+ }
+
+ devpriv->gpct_mode[chan] = 0;
+ devpriv->gpct_input_select[chan] = 0;
+ devpriv->gpct_command[chan] = 0;
+
+ devpriv->gpct_command[chan] |= G_Synchronized_Gate;
+
+ ni_stc_writew(dev, devpriv->gpct_mode[chan], G_Mode_Register(chan));
+ ni_stc_writew(dev, devpriv->gpct_input_select[chan],
+ G_Input_Select_Register(chan));
+ ni_stc_writew(dev, 0, G_Autoincrement_Register(chan));
+}
+#endif
+
+static irqreturn_t ni_E_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ unsigned short a_status;
+ unsigned short b_status;
+ unsigned int ai_mite_status = 0;
+ unsigned int ao_mite_status = 0;
+ unsigned long flags;
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ struct mite_struct *mite = devpriv->mite;
+#endif
+
+ if (!dev->attached)
+ return IRQ_NONE;
+ smp_mb(); /* make sure dev->attached is checked before handler does anything else. */
+
+ /* lock to avoid race with comedi_poll */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ a_status = ni_stc_readw(dev, AI_Status_1_Register);
+ b_status = ni_stc_readw(dev, AO_Status_1_Register);
+#ifdef PCIDMA
+ if (mite) {
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags_too;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags_too);
+ if (devpriv->ai_mite_chan) {
+ ai_mite_status = mite_get_status(devpriv->ai_mite_chan);
+ if (ai_mite_status & CHSR_LINKC)
+ writel(CHOR_CLRLC,
+ devpriv->mite->mite_io_addr +
+ MITE_CHOR(devpriv->
+ ai_mite_chan->channel));
+ }
+ if (devpriv->ao_mite_chan) {
+ ao_mite_status = mite_get_status(devpriv->ao_mite_chan);
+ if (ao_mite_status & CHSR_LINKC)
+ writel(CHOR_CLRLC,
+ mite->mite_io_addr +
+ MITE_CHOR(devpriv->
+ ao_mite_chan->channel));
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags_too);
+ }
+#endif
+ ack_a_interrupt(dev, a_status);
+ ack_b_interrupt(dev, b_status);
+ if ((a_status & Interrupt_A_St) || (ai_mite_status & CHSR_INT))
+ handle_a_interrupt(dev, a_status, ai_mite_status);
+ if ((b_status & Interrupt_B_St) || (ao_mite_status & CHSR_INT))
+ handle_b_interrupt(dev, b_status, ao_mite_status);
+ handle_gpct_interrupt(dev, 0);
+ handle_gpct_interrupt(dev, 1);
+ handle_cdio_interrupt(dev);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ return IRQ_HANDLED;
+}
+
+static int ni_alloc_private(struct comedi_device *dev)
+{
+ struct ni_private *devpriv;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->window_lock);
+ spin_lock_init(&devpriv->soft_reg_copy_lock);
+ spin_lock_init(&devpriv->mite_channel_lock);
+
+ return 0;
+}
+
+static int ni_E_init(struct comedi_device *dev,
+ unsigned interrupt_pin, unsigned irq_polarity)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ if (board->n_aochan > MAX_N_AO_CHAN) {
+ dev_err(dev->class_dev, "bug! n_aochan > MAX_N_AO_CHAN\n");
+ return -EINVAL;
+ }
+
+ /* initialize clock dividers */
+ devpriv->clock_and_fout = Slow_Internal_Time_Divide_By_2 |
+ Slow_Internal_Timebase |
+ Clock_To_Board_Divide_By_2 |
+ Clock_To_Board;
+ if (!devpriv->is_6xxx) {
+ /* BEAM is this needed for PCI-6143 ?? */
+ devpriv->clock_and_fout |= (AI_Output_Divide_By_2 |
+ AO_Output_Divide_By_2);
+ }
+ ni_stc_writew(dev, devpriv->clock_and_fout, Clock_and_FOUT_Register);
+
+ ret = comedi_alloc_subdevices(dev, NI_NUM_SUBDEVICES);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[NI_AI_SUBDEV];
+ if (board->n_adchan) {
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_DITHER;
+ if (!devpriv->is_611x)
+ s->subdev_flags |= SDF_GROUND | SDF_COMMON | SDF_OTHER;
+ if (board->ai_maxdata > 0xffff)
+ s->subdev_flags |= SDF_LSAMPL;
+ if (devpriv->is_m_series)
+ s->subdev_flags |= SDF_SOFT_CALIBRATED;
+ s->n_chan = board->n_adchan;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = ni_range_lkup[board->gainlkup];
+ s->insn_read = ni_ai_insn_read;
+ s->insn_config = ni_ai_insn_config;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 512;
+ s->do_cmdtest = ni_ai_cmdtest;
+ s->do_cmd = ni_ai_cmd;
+ s->cancel = ni_ai_reset;
+ s->poll = ni_ai_poll;
+ s->munge = ni_ai_munge;
+
+ if (devpriv->mite)
+ s->async_dma_dir = DMA_FROM_DEVICE;
+ }
+
+ /* reset the analog input configuration */
+ ni_ai_reset(dev, s);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[NI_AO_SUBDEV];
+ if (board->n_aochan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_DEGLITCH | SDF_GROUND;
+ if (devpriv->is_m_series)
+ s->subdev_flags |= SDF_SOFT_CALIBRATED;
+ s->n_chan = board->n_aochan;
+ s->maxdata = board->ao_maxdata;
+ s->range_table = board->ao_range_table;
+ s->insn_config = ni_ao_insn_config;
+ s->insn_write = ni_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /*
+ * Along with the IRQ we need either a FIFO or DMA for
+ * async command support.
+ */
+ if (dev->irq && (board->ao_fifo_depth || devpriv->mite)) {
+ dev->write_subdev = s;
+ s->subdev_flags |= SDF_CMD_WRITE;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = ni_ao_cmdtest;
+ s->do_cmd = ni_ao_cmd;
+ s->cancel = ni_ao_reset;
+ if (!devpriv->is_m_series)
+ s->munge = ni_ao_munge;
+
+ if (devpriv->mite)
+ s->async_dma_dir = DMA_TO_DEVICE;
+ }
+
+ if (devpriv->is_67xx)
+ init_ao_67xx(dev, s);
+
+ /* reset the analog output configuration */
+ ni_ao_reset(dev, s);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[NI_DIO_SUBDEV];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = board->has_32dio_chan ? 32 : 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ if (devpriv->is_m_series) {
+ s->subdev_flags |= SDF_LSAMPL;
+ s->insn_bits = ni_m_series_dio_insn_bits;
+ s->insn_config = ni_m_series_dio_insn_config;
+ if (dev->irq) {
+ s->subdev_flags |= SDF_CMD_WRITE /* | SDF_CMD_READ */;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = ni_cdio_cmdtest;
+ s->do_cmd = ni_cdio_cmd;
+ s->cancel = ni_cdio_cancel;
+
+ /* M-series boards use DMA */
+ s->async_dma_dir = DMA_BIDIRECTIONAL;
+ }
+
+ /* reset DIO and set all channels to inputs */
+ ni_writel(dev, CDO_Reset_Bit | CDI_Reset_Bit,
+ M_Offset_CDIO_Command);
+ ni_writel(dev, s->io_bits, M_Offset_DIO_Direction);
+ } else {
+ s->insn_bits = ni_dio_insn_bits;
+ s->insn_config = ni_dio_insn_config;
+
+ /* set all channels to inputs */
+ devpriv->dio_control = DIO_Pins_Dir(s->io_bits);
+ ni_writew(dev, devpriv->dio_control, DIO_Control_Register);
+ }
+
+ /* 8255 device */
+ s = &dev->subdevices[NI_8255_DIO_SUBDEV];
+ if (board->has_8255) {
+ ret = subdev_8255_init(dev, s, ni_8255_callback, Port_A);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* formerly general purpose counter/timer device, but no longer used */
+ s = &dev->subdevices[NI_UNUSED_SUBDEV];
+ s->type = COMEDI_SUBD_UNUSED;
+
+ /* Calibration subdevice */
+ s = &dev->subdevices[NI_CALIBRATION_SUBDEV];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_INTERNAL;
+ s->n_chan = 1;
+ s->maxdata = 0;
+ if (devpriv->is_m_series) {
+ /* internal PWM output used for AI nonlinearity calibration */
+ s->insn_config = ni_m_series_pwm_config;
+
+ ni_writel(dev, 0x0, M_Offset_Cal_PWM);
+ } else if (devpriv->is_6143) {
+ /* internal PWM output used for AI nonlinearity calibration */
+ s->insn_config = ni_6143_pwm_config;
+ } else {
+ s->subdev_flags |= SDF_WRITABLE;
+ s->insn_read = ni_calib_insn_read;
+ s->insn_write = ni_calib_insn_write;
+
+ /* setup the caldacs and find the real n_chan and maxdata */
+ caldac_setup(dev, s);
+ }
+
+ /* EEPROM subdevice */
+ s = &dev->subdevices[NI_EEPROM_SUBDEV];
+ s->type = COMEDI_SUBD_MEMORY;
+ s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
+ s->maxdata = 0xff;
+ if (devpriv->is_m_series) {
+ s->n_chan = M_SERIES_EEPROM_SIZE;
+ s->insn_read = ni_m_series_eeprom_insn_read;
+ } else {
+ s->n_chan = 512;
+ s->insn_read = ni_eeprom_insn_read;
+ }
+
+ /* Digital I/O (PFI) subdevice */
+ s = &dev->subdevices[NI_PFI_DIO_SUBDEV];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->maxdata = 1;
+ if (devpriv->is_m_series) {
+ s->n_chan = 16;
+ s->insn_bits = ni_pfi_insn_bits;
+
+ ni_writew(dev, s->state, M_Offset_PFI_DO);
+ for (i = 0; i < NUM_PFI_OUTPUT_SELECT_REGS; ++i) {
+ ni_writew(dev, devpriv->pfi_output_select_reg[i],
+ M_Offset_PFI_Output_Select(i + 1));
+ }
+ } else {
+ s->n_chan = 10;
+ }
+ s->insn_config = ni_pfi_insn_config;
+
+ ni_set_bits(dev, IO_Bidirection_Pin_Register, ~0, 0);
+
+ /* cs5529 calibration adc */
+ s = &dev->subdevices[NI_CS5529_CALIBRATION_SUBDEV];
+ if (devpriv->is_67xx) {
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_INTERNAL;
+ /* one channel for each analog output channel */
+ s->n_chan = board->n_aochan;
+ s->maxdata = (1 << 16) - 1;
+ s->range_table = &range_unknown; /* XXX */
+ s->insn_read = cs5529_ai_insn_read;
+ s->insn_config = NULL;
+ init_cs5529(dev);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Serial */
+ s = &dev->subdevices[NI_SERIAL_SUBDEV];
+ s->type = COMEDI_SUBD_SERIAL;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = 1;
+ s->maxdata = 0xff;
+ s->insn_config = ni_serial_insn_config;
+ devpriv->serial_interval_ns = 0;
+ devpriv->serial_hw_mode = 0;
+
+ /* RTSI */
+ s = &dev->subdevices[NI_RTSI_SUBDEV];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->insn_bits = ni_rtsi_insn_bits;
+ s->insn_config = ni_rtsi_insn_config;
+ ni_rtsi_init(dev);
+
+ /* allocate and initialize the gpct counter device */
+ devpriv->counter_dev = ni_gpct_device_construct(dev,
+ ni_gpct_write_register,
+ ni_gpct_read_register,
+ (devpriv->is_m_series)
+ ? ni_gpct_variant_m_series
+ : ni_gpct_variant_e_series,
+ NUM_GPCT);
+ if (!devpriv->counter_dev)
+ return -ENOMEM;
+
+ /* Counter (gpct) subdevices */
+ for (i = 0; i < NUM_GPCT; ++i) {
+ struct ni_gpct *gpct = &devpriv->counter_dev->counters[i];
+
+ /* setup and initialize the counter */
+ gpct->chip_index = 0;
+ gpct->counter_index = i;
+ ni_tio_init_counter(gpct);
+
+ s = &dev->subdevices[NI_GPCT_SUBDEV(i)];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
+ s->n_chan = 3;
+ s->maxdata = (devpriv->is_m_series) ? 0xffffffff
+ : 0x00ffffff;
+ s->insn_read = ni_tio_insn_read;
+ s->insn_write = ni_tio_insn_read;
+ s->insn_config = ni_tio_insn_config;
+#ifdef PCIDMA
+ if (dev->irq && devpriv->mite) {
+ s->subdev_flags |= SDF_CMD_READ /* | SDF_CMD_WRITE */;
+ s->len_chanlist = 1;
+ s->do_cmdtest = ni_tio_cmdtest;
+ s->do_cmd = ni_gpct_cmd;
+ s->cancel = ni_gpct_cancel;
+
+ s->async_dma_dir = DMA_BIDIRECTIONAL;
+ }
+#endif
+ s->private = gpct;
+ }
+
+ /* Frequency output subdevice */
+ s = &dev->subdevices[NI_FREQ_OUT_SUBDEV];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 1;
+ s->maxdata = 0xf;
+ s->insn_read = ni_freq_out_insn_read;
+ s->insn_write = ni_freq_out_insn_write;
+ s->insn_config = ni_freq_out_insn_config;
+
+ if (dev->irq) {
+ ni_stc_writew(dev,
+ (irq_polarity ? Interrupt_Output_Polarity : 0) |
+ (Interrupt_Output_On_3_Pins & 0) |
+ Interrupt_A_Enable | Interrupt_B_Enable |
+ Interrupt_A_Output_Select(interrupt_pin) |
+ Interrupt_B_Output_Select(interrupt_pin),
+ Interrupt_Control_Register);
+ }
+
+ /* DMA setup */
+ ni_writeb(dev, devpriv->ai_ao_select_reg, AI_AO_Select);
+ ni_writeb(dev, devpriv->g0_g1_select_reg, G0_G1_Select);
+
+ if (devpriv->is_6xxx) {
+ ni_writeb(dev, 0, Magic_611x);
+ } else if (devpriv->is_m_series) {
+ int channel;
+
+ for (channel = 0; channel < board->n_aochan; ++channel) {
+ ni_writeb(dev, 0xf,
+ M_Offset_AO_Waveform_Order(channel));
+ ni_writeb(dev, 0x0,
+ M_Offset_AO_Reference_Attenuation(channel));
+ }
+ ni_writeb(dev, 0x0, M_Offset_AO_Calibration);
+ }
+
+ return 0;
+}
+
+static void mio_common_detach(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->counter_dev)
+ ni_gpct_device_destroy(devpriv->counter_dev);
+ }
+}
diff --git a/drivers/staging/comedi/drivers/ni_mio_cs.c b/drivers/staging/comedi/drivers/ni_mio_cs.c
new file mode 100644
index 000000000..e3d821bf2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_mio_cs.c
@@ -0,0 +1,228 @@
+/*
+ comedi/drivers/ni_mio_cs.c
+ Hardware driver for NI PCMCIA MIO E series cards
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_mio_cs
+Description: National Instruments DAQCard E series
+Author: ds
+Status: works
+Devices: [National Instruments] DAQCard-AI-16XE-50 (ni_mio_cs),
+ DAQCard-AI-16E-4, DAQCard-6062E, DAQCard-6024E, DAQCard-6036E
+Updated: Thu Oct 23 19:43:17 CDT 2003
+
+See the notes in the ni_atmio.o driver.
+*/
+/*
+ The real guts of the driver is in ni_mio_common.c, which is
+ included by all the E series drivers.
+
+ References for specifications:
+
+ 341080a.pdf DAQCard E Series Register Level Programmer Manual
+
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "../comedi_pcmcia.h"
+#include "ni_stc.h"
+#include "8255.h"
+
+/*
+ * AT specific setup
+ */
+
+static const struct ni_board_struct ni_boards[] = {
+ {
+ .name = "DAQCard-ai-16xe-50",
+ .device_id = 0x010d,
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_8,
+ .ai_speed = 5000,
+ .caldac = { dac8800, dac8043 },
+ }, {
+ .name = "DAQCard-ai-16e-4",
+ .device_id = 0x010c,
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 4000,
+ .caldac = { mb88341 }, /* verified */
+ }, {
+ .name = "DAQCard-6062E",
+ .device_id = 0x02c4,
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 2000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1176,
+ .caldac = { ad8804_debug }, /* verified */
+ }, {
+ /* specs incorrect! */
+ .name = "DAQCard-6024E",
+ .device_id = 0x075e,
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000000,
+ .caldac = { ad8804_debug },
+ }, {
+ /* specs incorrect! */
+ .name = "DAQCard-6036E",
+ .device_id = 0x0245,
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 1024,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000000,
+ .caldac = { ad8804_debug },
+ },
+#if 0
+ {
+ .name = "DAQCard-6715",
+ .device_id = 0x0000, /* unknown */
+ .n_aochan = 8,
+ .ao_maxdata = 0x0fff,
+ .ao_671x = 8192,
+ .caldac = { mb88341, mb88341 },
+ },
+#endif
+};
+
+#include "ni_mio_common.c"
+
+static const void *ni_getboardtype(struct comedi_device *dev,
+ struct pcmcia_device *link)
+{
+ static const struct ni_board_struct *board;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ni_boards); i++) {
+ board = &ni_boards[i];
+ if (board->device_id == link->card_id)
+ return board;
+ }
+ return NULL;
+}
+
+static int mio_pcmcia_config_loop(struct pcmcia_device *p_dev, void *priv_data)
+{
+ int base, ret;
+
+ p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+ p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
+
+ for (base = 0x000; base < 0x400; base += 0x20) {
+ p_dev->resource[0]->start = base;
+ ret = pcmcia_request_io(p_dev);
+ if (!ret)
+ return 0;
+ }
+ return -ENODEV;
+}
+
+static int mio_cs_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ static const struct ni_board_struct *board;
+ int ret;
+
+ board = ni_getboardtype(dev, link);
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
+ ret = comedi_pcmcia_enable(dev, mio_pcmcia_config_loop);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ link->priv = dev;
+ ret = pcmcia_request_irq(link, ni_E_interrupt);
+ if (ret)
+ return ret;
+ dev->irq = link->irq;
+
+ ret = ni_alloc_private(dev);
+ if (ret)
+ return ret;
+
+ return ni_E_init(dev, 0, 1);
+}
+
+static void mio_cs_detach(struct comedi_device *dev)
+{
+ mio_common_detach(dev);
+ comedi_pcmcia_disable(dev);
+}
+
+static struct comedi_driver driver_ni_mio_cs = {
+ .driver_name = "ni_mio_cs",
+ .module = THIS_MODULE,
+ .auto_attach = mio_cs_auto_attach,
+ .detach = mio_cs_detach,
+};
+
+static int cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_ni_mio_cs);
+}
+
+static const struct pcmcia_device_id ni_mio_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010d), /* DAQCard-ai-16xe-50 */
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010c), /* DAQCard-ai-16e-4 */
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x02c4), /* DAQCard-6062E */
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x075e), /* DAQCard-6024E */
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0245), /* DAQCard-6036E */
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, ni_mio_cs_ids);
+
+static struct pcmcia_driver ni_mio_cs_driver = {
+ .name = "ni_mio_cs",
+ .owner = THIS_MODULE,
+ .id_table = ni_mio_cs_ids,
+ .probe = cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_ni_mio_cs, ni_mio_cs_driver);
+
+MODULE_DESCRIPTION("Comedi driver for National Instruments DAQCard E series");
+MODULE_AUTHOR("David A. Schleef <ds@schleef.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_pcidio.c b/drivers/staging/comedi/drivers/ni_pcidio.c
new file mode 100644
index 000000000..6453b7870
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_pcidio.c
@@ -0,0 +1,1024 @@
+/*
+ comedi/drivers/ni_pcidio.c
+ driver for National Instruments PCI-DIO-32HS
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999,2002 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_pcidio
+Description: National Instruments PCI-DIO32HS, PCI-6533
+Author: ds
+Status: works
+Devices: [National Instruments] PCI-DIO-32HS (ni_pcidio)
+ [National Instruments] PXI-6533, PCI-6533 (pxi-6533)
+ [National Instruments] PCI-6534 (pci-6534)
+Updated: Mon, 09 Jan 2012 14:27:23 +0000
+
+The DIO32HS board appears as one subdevice, with 32 channels.
+Each channel is individually I/O configurable. The channel order
+is 0=A0, 1=A1, 2=A2, ... 8=B0, 16=C0, 24=D0. The driver only
+supports simple digital I/O; no handshaking is supported.
+
+DMA mostly works for the PCI-DIO32HS, but only in timed input mode.
+
+The PCI-DIO-32HS/PCI-6533 has a configurable external trigger. Setting
+scan_begin_arg to 0 or CR_EDGE triggers on the leading edge. Setting
+scan_begin_arg to CR_INVERT or (CR_EDGE | CR_INVERT) triggers on the
+trailing edge.
+
+This driver could be easily modified to support AT-MIO32HS and
+AT-MIO96.
+
+The PCI-6534 requires a firmware upload after power-up to work, the
+firmware data and instructions for loading it with comedi_config
+it are contained in the
+comedi_nonfree_firmware tarball available from http://www.comedi.org
+*/
+
+#define USE_DMA
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include "../comedi_pci.h"
+
+#include "mite.h"
+
+/* defines for the PCI-DIO-32HS */
+
+#define Window_Address 4 /* W */
+#define Interrupt_And_Window_Status 4 /* R */
+#define IntStatus1 (1<<0)
+#define IntStatus2 (1<<1)
+#define WindowAddressStatus_mask 0x7c
+
+#define Master_DMA_And_Interrupt_Control 5 /* W */
+#define InterruptLine(x) ((x)&3)
+#define OpenInt (1<<2)
+#define Group_Status 5 /* R */
+#define DataLeft (1<<0)
+#define Req (1<<2)
+#define StopTrig (1<<3)
+
+#define Group_1_Flags 6 /* R */
+#define Group_2_Flags 7 /* R */
+#define TransferReady (1<<0)
+#define CountExpired (1<<1)
+#define Waited (1<<5)
+#define PrimaryTC (1<<6)
+#define SecondaryTC (1<<7)
+ /* #define SerialRose */
+ /* #define ReqRose */
+ /* #define Paused */
+
+#define Group_1_First_Clear 6 /* W */
+#define Group_2_First_Clear 7 /* W */
+#define ClearWaited (1<<3)
+#define ClearPrimaryTC (1<<4)
+#define ClearSecondaryTC (1<<5)
+#define DMAReset (1<<6)
+#define FIFOReset (1<<7)
+#define ClearAll 0xf8
+
+#define Group_1_FIFO 8 /* W */
+#define Group_2_FIFO 12 /* W */
+
+#define Transfer_Count 20
+#define Chip_ID_D 24
+#define Chip_ID_I 25
+#define Chip_ID_O 26
+#define Chip_Version 27
+#define Port_IO(x) (28+(x))
+#define Port_Pin_Directions(x) (32+(x))
+#define Port_Pin_Mask(x) (36+(x))
+#define Port_Pin_Polarities(x) (40+(x))
+
+#define Master_Clock_Routing 45
+#define RTSIClocking(x) (((x)&3)<<4)
+
+#define Group_1_Second_Clear 46 /* W */
+#define Group_2_Second_Clear 47 /* W */
+#define ClearExpired (1<<0)
+
+#define Port_Pattern(x) (48+(x))
+
+#define Data_Path 64
+#define FIFOEnableA (1<<0)
+#define FIFOEnableB (1<<1)
+#define FIFOEnableC (1<<2)
+#define FIFOEnableD (1<<3)
+#define Funneling(x) (((x)&3)<<4)
+#define GroupDirection (1<<7)
+
+#define Protocol_Register_1 65
+#define OpMode Protocol_Register_1
+#define RunMode(x) ((x)&7)
+#define Numbered (1<<3)
+
+#define Protocol_Register_2 66
+#define ClockReg Protocol_Register_2
+#define ClockLine(x) (((x)&3)<<5)
+#define InvertStopTrig (1<<7)
+#define DataLatching(x) (((x)&3)<<5)
+
+#define Protocol_Register_3 67
+#define Sequence Protocol_Register_3
+
+#define Protocol_Register_14 68 /* 16 bit */
+#define ClockSpeed Protocol_Register_14
+
+#define Protocol_Register_4 70
+#define ReqReg Protocol_Register_4
+#define ReqConditioning(x) (((x)&7)<<3)
+
+#define Protocol_Register_5 71
+#define BlockMode Protocol_Register_5
+
+#define FIFO_Control 72
+#define ReadyLevel(x) ((x)&7)
+
+#define Protocol_Register_6 73
+#define LinePolarities Protocol_Register_6
+#define InvertAck (1<<0)
+#define InvertReq (1<<1)
+#define InvertClock (1<<2)
+#define InvertSerial (1<<3)
+#define OpenAck (1<<4)
+#define OpenClock (1<<5)
+
+#define Protocol_Register_7 74
+#define AckSer Protocol_Register_7
+#define AckLine(x) (((x)&3)<<2)
+#define ExchangePins (1<<7)
+
+#define Interrupt_Control 75
+ /* bits same as flags */
+
+#define DMA_Line_Control_Group1 76
+#define DMA_Line_Control_Group2 108
+/* channel zero is none */
+static inline unsigned primary_DMAChannel_bits(unsigned channel)
+{
+ return channel & 0x3;
+}
+
+static inline unsigned secondary_DMAChannel_bits(unsigned channel)
+{
+ return (channel << 2) & 0xc;
+}
+
+#define Transfer_Size_Control 77
+#define TransferWidth(x) ((x)&3)
+#define TransferLength(x) (((x)&3)<<3)
+#define RequireRLevel (1<<5)
+
+#define Protocol_Register_15 79
+#define DAQOptions Protocol_Register_15
+#define StartSource(x) ((x)&0x3)
+#define InvertStart (1<<2)
+#define StopSource(x) (((x)&0x3)<<3)
+#define ReqStart (1<<6)
+#define PreStart (1<<7)
+
+#define Pattern_Detection 81
+#define DetectionMethod (1<<0)
+#define InvertMatch (1<<1)
+#define IE_Pattern_Detection (1<<2)
+
+#define Protocol_Register_9 82
+#define ReqDelay Protocol_Register_9
+
+#define Protocol_Register_10 83
+#define ReqNotDelay Protocol_Register_10
+
+#define Protocol_Register_11 84
+#define AckDelay Protocol_Register_11
+
+#define Protocol_Register_12 85
+#define AckNotDelay Protocol_Register_12
+
+#define Protocol_Register_13 86
+#define Data1Delay Protocol_Register_13
+
+#define Protocol_Register_8 88 /* 32 bit */
+#define StartDelay Protocol_Register_8
+
+/* Firmware files for PCI-6524 */
+#define FW_PCI_6534_MAIN "/*(DEBLOBBED)*/"
+#define FW_PCI_6534_SCARAB_DI "/*(DEBLOBBED)*/"
+#define FW_PCI_6534_SCARAB_DO "/*(DEBLOBBED)*/"
+/*(DEBLOBBED)*/
+
+enum pci_6534_firmware_registers { /* 16 bit */
+ Firmware_Control_Register = 0x100,
+ Firmware_Status_Register = 0x104,
+ Firmware_Data_Register = 0x108,
+ Firmware_Mask_Register = 0x10c,
+ Firmware_Debug_Register = 0x110,
+};
+/* main fpga registers (32 bit)*/
+enum pci_6534_fpga_registers {
+ FPGA_Control1_Register = 0x200,
+ FPGA_Control2_Register = 0x204,
+ FPGA_Irq_Mask_Register = 0x208,
+ FPGA_Status_Register = 0x20c,
+ FPGA_Signature_Register = 0x210,
+ FPGA_SCALS_Counter_Register = 0x280, /*write-clear */
+ FPGA_SCAMS_Counter_Register = 0x284, /*write-clear */
+ FPGA_SCBLS_Counter_Register = 0x288, /*write-clear */
+ FPGA_SCBMS_Counter_Register = 0x28c, /*write-clear */
+ FPGA_Temp_Control_Register = 0x2a0,
+ FPGA_DAR_Register = 0x2a8,
+ FPGA_ELC_Read_Register = 0x2b8,
+ FPGA_ELC_Write_Register = 0x2bc,
+};
+enum FPGA_Control_Bits {
+ FPGA_Enable_Bit = 0x8000,
+};
+
+#define TIMER_BASE 50 /* nanoseconds */
+
+#ifdef USE_DMA
+#define IntEn (CountExpired|Waited|PrimaryTC|SecondaryTC)
+#else
+#define IntEn (TransferReady|CountExpired|Waited|PrimaryTC|SecondaryTC)
+#endif
+
+enum nidio_boardid {
+ BOARD_PCIDIO_32HS,
+ BOARD_PXI6533,
+ BOARD_PCI6534,
+};
+
+struct nidio_board {
+ const char *name;
+ unsigned int uses_firmware:1;
+};
+
+static const struct nidio_board nidio_boards[] = {
+ [BOARD_PCIDIO_32HS] = {
+ .name = "pci-dio-32hs",
+ },
+ [BOARD_PXI6533] = {
+ .name = "pxi-6533",
+ },
+ [BOARD_PCI6534] = {
+ .name = "pci-6534",
+ .uses_firmware = 1,
+ },
+};
+
+struct nidio96_private {
+ struct mite_struct *mite;
+ int boardtype;
+ int dio;
+ unsigned short OpModeBits;
+ struct mite_channel *di_mite_chan;
+ struct mite_dma_descriptor_ring *di_mite_ring;
+ spinlock_t mite_channel_lock;
+};
+
+static int ni_pcidio_request_di_mite_channel(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ BUG_ON(devpriv->di_mite_chan);
+ devpriv->di_mite_chan =
+ mite_request_channel_in_range(devpriv->mite,
+ devpriv->di_mite_ring, 1, 2);
+ if (!devpriv->di_mite_chan) {
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ dev_err(dev->class_dev, "failed to reserve mite dma channel\n");
+ return -EBUSY;
+ }
+ devpriv->di_mite_chan->dir = COMEDI_INPUT;
+ writeb(primary_DMAChannel_bits(devpriv->di_mite_chan->channel) |
+ secondary_DMAChannel_bits(devpriv->di_mite_chan->channel),
+ dev->mmio + DMA_Line_Control_Group1);
+ mmiowb();
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ return 0;
+}
+
+static void ni_pcidio_release_di_mite_channel(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->di_mite_chan) {
+ mite_dma_disarm(devpriv->di_mite_chan);
+ mite_dma_reset(devpriv->di_mite_chan);
+ mite_release_channel(devpriv->di_mite_chan);
+ devpriv->di_mite_chan = NULL;
+ writeb(primary_DMAChannel_bits(0) |
+ secondary_DMAChannel_bits(0),
+ dev->mmio + DMA_Line_Control_Group1);
+ mmiowb();
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+}
+
+static int setup_mite_dma(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct nidio96_private *devpriv = dev->private;
+ int retval;
+ unsigned long flags;
+
+ retval = ni_pcidio_request_di_mite_channel(dev);
+ if (retval)
+ return retval;
+
+ /* write alloc the entire buffer */
+ comedi_buf_write_alloc(s, s->async->prealloc_bufsz);
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->di_mite_chan) {
+ mite_prep_dma(devpriv->di_mite_chan, 32, 32);
+ mite_dma_arm(devpriv->di_mite_chan);
+ } else {
+ retval = -EIO;
+ }
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+
+ return retval;
+}
+
+static int ni_pcidio_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct nidio96_private *devpriv = dev->private;
+ unsigned long irq_flags;
+ int count;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ spin_lock(&devpriv->mite_channel_lock);
+ if (devpriv->di_mite_chan)
+ mite_sync_input_dma(devpriv->di_mite_chan, s);
+ spin_unlock(&devpriv->mite_channel_lock);
+ count = comedi_buf_n_bytes_ready(s);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ return count;
+}
+
+static irqreturn_t nidio_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct nidio96_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct mite_struct *mite = devpriv->mite;
+ unsigned int auxdata;
+ int flags;
+ int status;
+ int work = 0;
+ unsigned int m_status = 0;
+
+ /* interrupcions parasites */
+ if (!dev->attached) {
+ /* assume it's from another card */
+ return IRQ_NONE;
+ }
+
+ /* Lock to avoid race with comedi_poll */
+ spin_lock(&dev->spinlock);
+
+ status = readb(dev->mmio + Interrupt_And_Window_Status);
+ flags = readb(dev->mmio + Group_1_Flags);
+
+ spin_lock(&devpriv->mite_channel_lock);
+ if (devpriv->di_mite_chan)
+ m_status = mite_get_status(devpriv->di_mite_chan);
+
+ if (m_status & CHSR_INT) {
+ if (m_status & CHSR_LINKC) {
+ writel(CHOR_CLRLC,
+ mite->mite_io_addr +
+ MITE_CHOR(devpriv->di_mite_chan->channel));
+ mite_sync_input_dma(devpriv->di_mite_chan, s);
+ /* XXX need to byteswap */
+ }
+ if (m_status & ~(CHSR_INT | CHSR_LINKC | CHSR_DONE | CHSR_DRDY |
+ CHSR_DRQ1 | CHSR_MRDY)) {
+ dev_dbg(dev->class_dev,
+ "unknown mite interrupt, disabling IRQ\n");
+ async->events |= COMEDI_CB_ERROR;
+ disable_irq(dev->irq);
+ }
+ }
+ spin_unlock(&devpriv->mite_channel_lock);
+
+ while (status & DataLeft) {
+ work++;
+ if (work > 20) {
+ dev_dbg(dev->class_dev, "too much work in interrupt\n");
+ writeb(0x00,
+ dev->mmio + Master_DMA_And_Interrupt_Control);
+ break;
+ }
+
+ flags &= IntEn;
+
+ if (flags & TransferReady) {
+ while (flags & TransferReady) {
+ work++;
+ if (work > 100) {
+ dev_dbg(dev->class_dev,
+ "too much work in interrupt\n");
+ writeb(0x00, dev->mmio +
+ Master_DMA_And_Interrupt_Control
+ );
+ goto out;
+ }
+ auxdata = readl(dev->mmio + Group_1_FIFO);
+ comedi_buf_write_samples(s, &auxdata, 1);
+ flags = readb(dev->mmio + Group_1_Flags);
+ }
+ }
+
+ if (flags & CountExpired) {
+ writeb(ClearExpired, dev->mmio + Group_1_Second_Clear);
+ async->events |= COMEDI_CB_EOA;
+
+ writeb(0x00, dev->mmio + OpMode);
+ break;
+ } else if (flags & Waited) {
+ writeb(ClearWaited, dev->mmio + Group_1_First_Clear);
+ async->events |= COMEDI_CB_ERROR;
+ break;
+ } else if (flags & PrimaryTC) {
+ writeb(ClearPrimaryTC,
+ dev->mmio + Group_1_First_Clear);
+ async->events |= COMEDI_CB_EOA;
+ } else if (flags & SecondaryTC) {
+ writeb(ClearSecondaryTC,
+ dev->mmio + Group_1_First_Clear);
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ flags = readb(dev->mmio + Group_1_Flags);
+ status = readb(dev->mmio + Interrupt_And_Window_Status);
+ }
+
+out:
+ comedi_handle_events(dev, s);
+#if 0
+ if (!tag)
+ writeb(0x03, dev->mmio + Master_DMA_And_Interrupt_Control);
+#endif
+
+ spin_unlock(&dev->spinlock);
+ return IRQ_HANDLED;
+}
+
+static int ni_pcidio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ writel(s->io_bits, dev->mmio + Port_Pin_Directions(0));
+
+ return insn->n;
+}
+
+static int ni_pcidio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ writel(s->state, dev->mmio + Port_IO(0));
+
+ data[1] = readl(dev->mmio + Port_IO(0));
+
+ return insn->n;
+}
+
+static int ni_pcidio_ns_to_timer(int *nanosec, unsigned int flags)
+{
+ int divider, base;
+
+ base = TIMER_BASE;
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ divider = (*nanosec + base / 2) / base;
+ break;
+ case CMDF_ROUND_DOWN:
+ divider = (*nanosec) / base;
+ break;
+ case CMDF_ROUND_UP:
+ divider = (*nanosec + base - 1) / base;
+ break;
+ }
+
+ *nanosec = base * divider;
+ return divider;
+}
+
+static int ni_pcidio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SPEED (TIMER_BASE) /* in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED);
+ /* no minimum speed */
+ } else {
+ /* TRIG_EXT */
+ /* should be level/edge, hi/lo specification here */
+ if ((cmd->scan_begin_arg & ~(CR_EDGE | CR_INVERT)) != 0) {
+ cmd->scan_begin_arg &= (CR_EDGE | CR_INVERT);
+ err |= -EINVAL;
+ }
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ ni_pcidio_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int ni_pcidio_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct nidio96_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ writeb(devpriv->OpModeBits, dev->mmio + OpMode);
+ s->async->inttrig = NULL;
+
+ return 1;
+}
+
+static int ni_pcidio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct nidio96_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ /* XXX configure ports for input */
+ writel(0x0000, dev->mmio + Port_Pin_Directions(0));
+
+ if (1) {
+ /* enable fifos A B C D */
+ writeb(0x0f, dev->mmio + Data_Path);
+
+ /* set transfer width a 32 bits */
+ writeb(TransferWidth(0) | TransferLength(0),
+ dev->mmio + Transfer_Size_Control);
+ } else {
+ writeb(0x03, dev->mmio + Data_Path);
+ writeb(TransferWidth(3) | TransferLength(0),
+ dev->mmio + Transfer_Size_Control);
+ }
+
+ /* protocol configuration */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* page 4-5, "input with internal REQs" */
+ writeb(0, dev->mmio + OpMode);
+ writeb(0x00, dev->mmio + ClockReg);
+ writeb(1, dev->mmio + Sequence);
+ writeb(0x04, dev->mmio + ReqReg);
+ writeb(4, dev->mmio + BlockMode);
+ writeb(3, dev->mmio + LinePolarities);
+ writeb(0xc0, dev->mmio + AckSer);
+ writel(ni_pcidio_ns_to_timer(&cmd->scan_begin_arg,
+ CMDF_ROUND_NEAREST),
+ dev->mmio + StartDelay);
+ writeb(1, dev->mmio + ReqDelay);
+ writeb(1, dev->mmio + ReqNotDelay);
+ writeb(1, dev->mmio + AckDelay);
+ writeb(0x0b, dev->mmio + AckNotDelay);
+ writeb(0x01, dev->mmio + Data1Delay);
+ /* manual, page 4-5: ClockSpeed comment is incorrectly listed
+ * on DAQOptions */
+ writew(0, dev->mmio + ClockSpeed);
+ writeb(0, dev->mmio + DAQOptions);
+ } else {
+ /* TRIG_EXT */
+ /* page 4-5, "input with external REQs" */
+ writeb(0, dev->mmio + OpMode);
+ writeb(0x00, dev->mmio + ClockReg);
+ writeb(0, dev->mmio + Sequence);
+ writeb(0x00, dev->mmio + ReqReg);
+ writeb(4, dev->mmio + BlockMode);
+ if (!(cmd->scan_begin_arg & CR_INVERT)) /* Leading Edge */
+ writeb(0, dev->mmio + LinePolarities);
+ else /* Trailing Edge */
+ writeb(2, dev->mmio + LinePolarities);
+ writeb(0x00, dev->mmio + AckSer);
+ writel(1, dev->mmio + StartDelay);
+ writeb(1, dev->mmio + ReqDelay);
+ writeb(1, dev->mmio + ReqNotDelay);
+ writeb(1, dev->mmio + AckDelay);
+ writeb(0x0C, dev->mmio + AckNotDelay);
+ writeb(0x10, dev->mmio + Data1Delay);
+ writew(0, dev->mmio + ClockSpeed);
+ writeb(0x60, dev->mmio + DAQOptions);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ writel(cmd->stop_arg,
+ dev->mmio + Transfer_Count);
+ } else {
+ /* XXX */
+ }
+
+#ifdef USE_DMA
+ writeb(ClearPrimaryTC | ClearSecondaryTC,
+ dev->mmio + Group_1_First_Clear);
+
+ {
+ int retval = setup_mite_dma(dev, s);
+
+ if (retval)
+ return retval;
+ }
+#else
+ writeb(0x00, dev->mmio + DMA_Line_Control_Group1);
+#endif
+ writeb(0x00, dev->mmio + DMA_Line_Control_Group2);
+
+ /* clear and enable interrupts */
+ writeb(0xff, dev->mmio + Group_1_First_Clear);
+ /* writeb(ClearExpired, dev->mmio+Group_1_Second_Clear); */
+
+ writeb(IntEn, dev->mmio + Interrupt_Control);
+ writeb(0x03, dev->mmio + Master_DMA_And_Interrupt_Control);
+
+ if (cmd->stop_src == TRIG_NONE) {
+ devpriv->OpModeBits = DataLatching(0) | RunMode(7);
+ } else { /* TRIG_TIMER */
+ devpriv->OpModeBits = Numbered | RunMode(7);
+ }
+ if (cmd->start_src == TRIG_NOW) {
+ /* start */
+ writeb(devpriv->OpModeBits, dev->mmio + OpMode);
+ s->async->inttrig = NULL;
+ } else {
+ /* TRIG_INT */
+ s->async->inttrig = ni_pcidio_inttrig;
+ }
+
+ return 0;
+}
+
+static int ni_pcidio_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ writeb(0x00, dev->mmio + Master_DMA_And_Interrupt_Control);
+ ni_pcidio_release_di_mite_channel(dev);
+
+ return 0;
+}
+
+static int ni_pcidio_change(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct nidio96_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->di_mite_ring, s);
+ if (ret < 0)
+ return ret;
+
+ memset(s->async->prealloc_buf, 0xaa, s->async->prealloc_bufsz);
+
+ return 0;
+}
+
+static int pci_6534_load_fpga(struct comedi_device *dev,
+ const u8 *data, size_t data_len,
+ unsigned long context)
+{
+ static const int timeout = 1000;
+ int fpga_index = context;
+ int i;
+ size_t j;
+
+ writew(0x80 | fpga_index, dev->mmio + Firmware_Control_Register);
+ writew(0xc0 | fpga_index, dev->mmio + Firmware_Control_Register);
+ for (i = 0;
+ (readw(dev->mmio + Firmware_Status_Register) & 0x2) == 0 &&
+ i < timeout; ++i) {
+ udelay(1);
+ }
+ if (i == timeout) {
+ dev_warn(dev->class_dev,
+ "ni_pcidio: failed to load fpga %i, waiting for status 0x2\n",
+ fpga_index);
+ return -EIO;
+ }
+ writew(0x80 | fpga_index, dev->mmio + Firmware_Control_Register);
+ for (i = 0;
+ readw(dev->mmio + Firmware_Status_Register) != 0x3 &&
+ i < timeout; ++i) {
+ udelay(1);
+ }
+ if (i == timeout) {
+ dev_warn(dev->class_dev,
+ "ni_pcidio: failed to load fpga %i, waiting for status 0x3\n",
+ fpga_index);
+ return -EIO;
+ }
+ for (j = 0; j + 1 < data_len;) {
+ unsigned int value = data[j++];
+
+ value |= data[j++] << 8;
+ writew(value, dev->mmio + Firmware_Data_Register);
+ for (i = 0;
+ (readw(dev->mmio + Firmware_Status_Register) & 0x2) == 0
+ && i < timeout; ++i) {
+ udelay(1);
+ }
+ if (i == timeout) {
+ dev_warn(dev->class_dev,
+ "ni_pcidio: failed to load word into fpga %i\n",
+ fpga_index);
+ return -EIO;
+ }
+ if (need_resched())
+ schedule();
+ }
+ writew(0x0, dev->mmio + Firmware_Control_Register);
+ return 0;
+}
+
+static int pci_6534_reset_fpga(struct comedi_device *dev, int fpga_index)
+{
+ return pci_6534_load_fpga(dev, NULL, 0, fpga_index);
+}
+
+static int pci_6534_reset_fpgas(struct comedi_device *dev)
+{
+ int ret;
+ int i;
+
+ writew(0x0, dev->mmio + Firmware_Control_Register);
+ for (i = 0; i < 3; ++i) {
+ ret = pci_6534_reset_fpga(dev, i);
+ if (ret < 0)
+ break;
+ }
+ writew(0x0, dev->mmio + Firmware_Mask_Register);
+ return ret;
+}
+
+static void pci_6534_init_main_fpga(struct comedi_device *dev)
+{
+ writel(0, dev->mmio + FPGA_Control1_Register);
+ writel(0, dev->mmio + FPGA_Control2_Register);
+ writel(0, dev->mmio + FPGA_SCALS_Counter_Register);
+ writel(0, dev->mmio + FPGA_SCAMS_Counter_Register);
+ writel(0, dev->mmio + FPGA_SCBLS_Counter_Register);
+ writel(0, dev->mmio + FPGA_SCBMS_Counter_Register);
+}
+
+static int pci_6534_upload_firmware(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+ static const char *const fw_file[3] = {
+ FW_PCI_6534_SCARAB_DI, /* loaded into scarab A for DI */
+ FW_PCI_6534_SCARAB_DO, /* loaded into scarab B for DO */
+ FW_PCI_6534_MAIN, /* loaded into main FPGA */
+ };
+ int ret;
+ int n;
+
+ ret = pci_6534_reset_fpgas(dev);
+ if (ret < 0)
+ return ret;
+ /* load main FPGA first, then the two scarabs */
+ for (n = 2; n >= 0; n--) {
+ ret = comedi_load_firmware(dev, &devpriv->mite->pcidev->dev,
+ fw_file[n],
+ pci_6534_load_fpga, n);
+ if (ret == 0 && n == 2)
+ pci_6534_init_main_fpga(dev);
+ if (ret < 0)
+ break;
+ }
+ return ret;
+}
+
+static void nidio_reset_board(struct comedi_device *dev)
+{
+ writel(0, dev->mmio + Port_IO(0));
+ writel(0, dev->mmio + Port_Pin_Directions(0));
+ writel(0, dev->mmio + Port_Pin_Mask(0));
+
+ /* disable interrupts on board */
+ writeb(0, dev->mmio + Master_DMA_And_Interrupt_Control);
+}
+
+static int nidio_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct nidio_board *board = NULL;
+ struct nidio96_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ unsigned int irq;
+
+ if (context < ARRAY_SIZE(nidio_boards))
+ board = &nidio_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->mite_channel_lock);
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ ret = mite_setup(dev, devpriv->mite);
+ if (ret < 0)
+ return ret;
+
+ devpriv->di_mite_ring = mite_alloc_ring(devpriv->mite);
+ if (!devpriv->di_mite_ring)
+ return -ENOMEM;
+
+ if (board->uses_firmware) {
+ ret = pci_6534_upload_firmware(dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ nidio_reset_board(dev);
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ dev_info(dev->class_dev, "%s rev=%d\n", dev->board_name,
+ readb(dev->mmio + Chip_Version));
+
+ s = &dev->subdevices[0];
+
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags =
+ SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | SDF_PACKED |
+ SDF_CMD_READ;
+ s->n_chan = 32;
+ s->range_table = &range_digital;
+ s->maxdata = 1;
+ s->insn_config = &ni_pcidio_insn_config;
+ s->insn_bits = &ni_pcidio_insn_bits;
+ s->do_cmd = &ni_pcidio_cmd;
+ s->do_cmdtest = &ni_pcidio_cmdtest;
+ s->cancel = &ni_pcidio_cancel;
+ s->len_chanlist = 32; /* XXX */
+ s->buf_change = &ni_pcidio_change;
+ s->async_dma_dir = DMA_BIDIRECTIONAL;
+ s->poll = &ni_pcidio_poll;
+
+ irq = pcidev->irq;
+ if (irq) {
+ ret = request_irq(irq, nidio_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ return 0;
+}
+
+static void nidio_detach(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->di_mite_ring) {
+ mite_free_ring(devpriv->di_mite_ring);
+ devpriv->di_mite_ring = NULL;
+ }
+ mite_detach(devpriv->mite);
+ }
+ if (dev->mmio)
+ iounmap(dev->mmio);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver ni_pcidio_driver = {
+ .driver_name = "ni_pcidio",
+ .module = THIS_MODULE,
+ .auto_attach = nidio_auto_attach,
+ .detach = nidio_detach,
+};
+
+static int ni_pcidio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_pcidio_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_pcidio_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1150), BOARD_PCIDIO_32HS },
+ { PCI_VDEVICE(NI, 0x12b0), BOARD_PCI6534 },
+ { PCI_VDEVICE(NI, 0x1320), BOARD_PXI6533 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_pcidio_pci_table);
+
+static struct pci_driver ni_pcidio_pci_driver = {
+ .name = "ni_pcidio",
+ .id_table = ni_pcidio_pci_table,
+ .probe = ni_pcidio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_pcidio_driver, ni_pcidio_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_pcimio.c b/drivers/staging/comedi/drivers/ni_pcimio.c
new file mode 100644
index 000000000..1481f71a3
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_pcimio.c
@@ -0,0 +1,1308 @@
+/*
+ comedi/drivers/ni_pcimio.c
+ Hardware driver for NI PCI-MIO E series cards
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ni_pcimio
+Description: National Instruments PCI-MIO-E series and M series (all boards)
+Author: ds, John Hallen, Frank Mori Hess, Rolf Mueller, Herbert Peremans,
+ Herman Bruyninckx, Terry Barnaby
+Status: works
+Devices: [National Instruments] PCI-MIO-16XE-50 (ni_pcimio),
+ PCI-MIO-16XE-10, PXI-6030E, PCI-MIO-16E-1, PCI-MIO-16E-4, PCI-6014, PCI-6040E,
+ PXI-6040E, PCI-6030E, PCI-6031E, PCI-6032E, PCI-6033E, PCI-6071E, PCI-6023E,
+ PCI-6024E, PCI-6025E, PXI-6025E, PCI-6034E, PCI-6035E, PCI-6052E,
+ PCI-6110, PCI-6111, PCI-6220, PCI-6221, PCI-6224, PXI-6224,
+ PCI-6225, PXI-6225, PCI-6229, PCI-6250, PCI-6251, PCIe-6251, PXIe-6251,
+ PCI-6254, PCI-6259, PCIe-6259,
+ PCI-6280, PCI-6281, PXI-6281, PCI-6284, PCI-6289,
+ PCI-6711, PXI-6711, PCI-6713, PXI-6713,
+ PXI-6071E, PCI-6070E, PXI-6070E,
+ PXI-6052E, PCI-6036E, PCI-6731, PCI-6733, PXI-6733,
+ PCI-6143, PXI-6143
+Updated: Mon, 09 Jan 2012 14:52:48 +0000
+
+These boards are almost identical to the AT-MIO E series, except that
+they use the PCI bus instead of ISA (i.e., AT). See the notes for
+the ni_atmio.o driver for additional information about these boards.
+
+Autocalibration is supported on many of the devices, using the
+comedi_calibrate (or comedi_soft_calibrate for m-series) utility.
+M-Series boards do analog input and analog output calibration entirely
+in software. The software calibration corrects
+the analog input for offset, gain and
+nonlinearity. The analog outputs are corrected for offset and gain.
+See the comedilib documentation on comedi_get_softcal_converter() for
+more information.
+
+By default, the driver uses DMA to transfer analog input data to
+memory. When DMA is enabled, not all triggering features are
+supported.
+
+Digital I/O may not work on 673x.
+
+Note that the PCI-6143 is a simultaineous sampling device with 8 convertors.
+With this board all of the convertors perform one simultaineous sample during
+a scan interval. The period for a scan is used for the convert time in a
+Comedi cmd. The convert trigger source is normally set to TRIG_NOW by default.
+
+The RTSI trigger bus is supported on these cards on
+subdevice 10. See the comedilib documentation for details.
+
+Information (number of channels, bits, etc.) for some devices may be
+incorrect. Please check this and submit a bug if there are problems
+for your device.
+
+SCXI is probably broken for m-series boards.
+
+Bugs:
+ - When DMA is enabled, COMEDI_EV_CONVERT does
+ not work correctly.
+
+*/
+/*
+ The PCI-MIO E series driver was originally written by
+ Tomasz Motylewski <...>, and ported to comedi by ds.
+
+ References:
+
+ 341079b.pdf PCI E Series Register-Level Programmer Manual
+ 340934b.pdf DAQ-STC reference manual
+
+ 322080b.pdf 6711/6713/6715 User Manual
+
+ 320945c.pdf PCI E Series User Manual
+ 322138a.pdf PCI-6052E and DAQPad-6052E User Manual
+
+ ISSUES:
+
+ need to deal with external reference for DAC, and other DAC
+ properties in board properties
+
+ deal with at-mio-16de-10 revision D to N changes, etc.
+
+ need to add other CALDAC type
+
+ need to slow down DAC loading. I don't trust NI's claim that
+ two writes to the PCI bus slows IO enough. I would prefer to
+ use udelay(). Timing specs: (clock)
+ AD8522 30ns
+ DAC8043 120ns
+ DAC8800 60ns
+ MB88341 ?
+
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "../comedi_pci.h"
+
+#include <asm/byteorder.h>
+
+#include "ni_stc.h"
+#include "mite.h"
+
+#define PCIDMA
+
+/* These are not all the possible ao ranges for 628x boards.
+ They can do OFFSET +- REFERENCE where OFFSET can be
+ 0V, 5V, APFI<0,1>, or AO<0...3> and RANGE can
+ be 10V, 5V, 2V, 1V, APFI<0,1>, AO<0...3>. That's
+ 63 different possibilities. An AO channel
+ can not act as it's own OFFSET or REFERENCE.
+*/
+static const struct comedi_lrange range_ni_M_628x_ao = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ RANGE(-5, 15),
+ UNI_RANGE(10),
+ RANGE(3, 7),
+ RANGE(4, 6),
+ RANGE_ext(-1, 1)
+ }
+};
+
+static const struct comedi_lrange range_ni_M_625x_ao = {
+ 3, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ RANGE_ext(-1, 1)
+ }
+};
+
+enum ni_pcimio_boardid {
+ BOARD_PCIMIO_16XE_50,
+ BOARD_PCIMIO_16XE_10,
+ BOARD_PCI6014,
+ BOARD_PXI6030E,
+ BOARD_PCIMIO_16E_1,
+ BOARD_PCIMIO_16E_4,
+ BOARD_PXI6040E,
+ BOARD_PCI6031E,
+ BOARD_PCI6032E,
+ BOARD_PCI6033E,
+ BOARD_PCI6071E,
+ BOARD_PCI6023E,
+ BOARD_PCI6024E,
+ BOARD_PCI6025E,
+ BOARD_PXI6025E,
+ BOARD_PCI6034E,
+ BOARD_PCI6035E,
+ BOARD_PCI6052E,
+ BOARD_PCI6110,
+ BOARD_PCI6111,
+ /* BOARD_PCI6115, */
+ /* BOARD_PXI6115, */
+ BOARD_PCI6711,
+ BOARD_PXI6711,
+ BOARD_PCI6713,
+ BOARD_PXI6713,
+ BOARD_PCI6731,
+ /* BOARD_PXI6731, */
+ BOARD_PCI6733,
+ BOARD_PXI6733,
+ BOARD_PXI6071E,
+ BOARD_PXI6070E,
+ BOARD_PXI6052E,
+ BOARD_PXI6031E,
+ BOARD_PCI6036E,
+ BOARD_PCI6220,
+ BOARD_PCI6221,
+ BOARD_PCI6221_37PIN,
+ BOARD_PCI6224,
+ BOARD_PXI6224,
+ BOARD_PCI6225,
+ BOARD_PXI6225,
+ BOARD_PCI6229,
+ BOARD_PCI6250,
+ BOARD_PCI6251,
+ BOARD_PCIE6251,
+ BOARD_PXIE6251,
+ BOARD_PCI6254,
+ BOARD_PCI6259,
+ BOARD_PCIE6259,
+ BOARD_PCI6280,
+ BOARD_PCI6281,
+ BOARD_PXI6281,
+ BOARD_PCI6284,
+ BOARD_PCI6289,
+ BOARD_PCI6143,
+ BOARD_PXI6143,
+};
+
+static const struct ni_board_struct ni_boards[] = {
+ [BOARD_PCIMIO_16XE_50] = {
+ .name = "pci-mio-16xe-50",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 2048,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_8,
+ .ai_speed = 50000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 50000,
+ .caldac = { dac8800, dac8043 },
+ },
+ [BOARD_PCIMIO_16XE_10] = {
+ .name = "pci-mio-16xe-10", /* aka pci-6030E */
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 10000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCI6014] = {
+ .name = "pci-6014",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PXI6030E] = {
+ .name = "pxi-6030e",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 10000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCIMIO_16E_1] = {
+ .name = "pci-mio-16e-1", /* aka pci-6070e */
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { mb88341 },
+ },
+ [BOARD_PCIMIO_16E_4] = {
+ .name = "pci-mio-16e-4", /* aka pci-6040e */
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_16,
+ /*
+ * there have been reported problems with
+ * full speed on this board
+ */
+ .ai_speed = 2000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 512,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { ad8804_debug }, /* doc says mb88341 */
+ },
+ [BOARD_PXI6040E] = {
+ .name = "pxi-6040e",
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 2000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 512,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { mb88341 },
+ },
+ [BOARD_PCI6031E] = {
+ .name = "pci-6031e",
+ .n_adchan = 64,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 10000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCI6032E] = {
+ .name = "pci-6032e",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCI6033E] = {
+ .name = "pci-6033e",
+ .n_adchan = 64,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCI6071E] = {
+ .name = "pci-6071e",
+ .n_adchan = 64,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6023E] = {
+ .name = "pci-6023e",
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .caldac = { ad8804_debug }, /* manual is wrong */
+ },
+ [BOARD_PCI6024E] = {
+ .name = "pci-6024e",
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .caldac = { ad8804_debug }, /* manual is wrong */
+ },
+ [BOARD_PCI6025E] = {
+ .name = "pci-6025e",
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .caldac = { ad8804_debug }, /* manual is wrong */
+ .has_8255 = 1,
+ },
+ [BOARD_PXI6025E] = {
+ .name = "pxi-6025e",
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 100000,
+ .caldac = { ad8804_debug }, /* manual is wrong */
+ .has_8255 = 1,
+ },
+ [BOARD_PCI6034E] = {
+ .name = "pci-6034e",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6035E] = {
+ .name = "pci-6035e",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6052E] = {
+ .name = "pci-6052e",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 3000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 3000,
+ /* manual is wrong */
+ .caldac = { ad8804_debug, ad8804_debug, ad8522 },
+ },
+ [BOARD_PCI6110] = {
+ .name = "pci-6110",
+ .n_adchan = 4,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 8192,
+ .alwaysdither = 0,
+ .gainlkup = ai_gain_611x,
+ .ai_speed = 200,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .reg_type = ni_reg_611x,
+ .ao_range_table = &range_bipolar10,
+ .ao_fifo_depth = 2048,
+ .ao_speed = 250,
+ .caldac = { ad8804, ad8804 },
+ },
+ [BOARD_PCI6111] = {
+ .name = "pci-6111",
+ .n_adchan = 2,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_611x,
+ .ai_speed = 200,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .reg_type = ni_reg_611x,
+ .ao_range_table = &range_bipolar10,
+ .ao_fifo_depth = 2048,
+ .ao_speed = 250,
+ .caldac = { ad8804, ad8804 },
+ },
+#if 0
+ /* The 6115 boards probably need their own driver */
+ [BOARD_PCI6115] = { /* .device_id = 0x2ed0, */
+ .name = "pci-6115",
+ .n_adchan = 4,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_611x,
+ .ai_speed = 100,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_671x = 1,
+ .ao_fifo_depth = 2048,
+ .ao_speed = 250,
+ .reg_611x = 1,
+ /* XXX */
+ .caldac = { ad8804_debug, ad8804_debug, ad8804_debug },
+ },
+#endif
+#if 0
+ [BOARD_PXI6115] = { /* .device_id = ????, */
+ .name = "pxi-6115",
+ .n_adchan = 4,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_611x,
+ .ai_speed = 100,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_671x = 1,
+ .ao_fifo_depth = 2048,
+ .ao_speed = 250,
+ .reg_611x = 1,
+ /* XXX */
+ .caldac = { ad8804_debug, ad8804_debug, ad8804_debug },
+ },
+#endif
+ [BOARD_PCI6711] = {
+ .name = "pci-6711",
+ .n_aochan = 4,
+ .ao_maxdata = 0x0fff,
+ /* data sheet says 8192, but fifo really holds 16384 samples */
+ .ao_fifo_depth = 16384,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000,
+ .reg_type = ni_reg_6711,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PXI6711] = {
+ .name = "pxi-6711",
+ .n_aochan = 4,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 16384,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000,
+ .reg_type = ni_reg_6711,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6713] = {
+ .name = "pci-6713",
+ .n_aochan = 8,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 16384,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000,
+ .reg_type = ni_reg_6713,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+ [BOARD_PXI6713] = {
+ .name = "pxi-6713",
+ .n_aochan = 8,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 16384,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000,
+ .reg_type = ni_reg_6713,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+ [BOARD_PCI6731] = {
+ .name = "pci-6731",
+ .n_aochan = 4,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8192,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000,
+ .reg_type = ni_reg_6711,
+ .caldac = { ad8804_debug },
+ },
+#if 0
+ [BOARD_PXI6731] = { /* .device_id = ????, */
+ .name = "pxi-6731",
+ .n_aochan = 4,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8192,
+ .ao_range_table = &range_bipolar10,
+ .reg_type = ni_reg_6711,
+ .caldac = { ad8804_debug },
+ },
+#endif
+ [BOARD_PCI6733] = {
+ .name = "pci-6733",
+ .n_aochan = 8,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 16384,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000,
+ .reg_type = ni_reg_6713,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+ [BOARD_PXI6733] = {
+ .name = "pxi-6733",
+ .n_aochan = 8,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 16384,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000,
+ .reg_type = ni_reg_6713,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+ [BOARD_PXI6071E] = {
+ .name = "pxi-6071e",
+ .n_adchan = 64,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PXI6070E] = {
+ .name = "pxi-6070e",
+ .n_adchan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0x0fff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 1000,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PXI6052E] = {
+ .name = "pxi-6052e",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 3000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 3000,
+ .caldac = { mb88341, mb88341, ad8522 },
+ },
+ [BOARD_PXI6031E] = {
+ .name = "pxi-6031e",
+ .n_adchan = 64,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_ni_E_ao_ext,
+ .ao_speed = 10000,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCI6036E] = {
+ .name = "pci-6036e",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6220] = {
+ .name = "pci-6220",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 512, /* FIXME: guess */
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_622x,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6221] = {
+ .name = "pci-6221",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6221_37PIN] = {
+ .name = "pci-6221_37pin",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6224] = {
+ .name = "pci-6224",
+ .n_adchan = 32,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_622x,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXI6224] = {
+ .name = "pxi-6224",
+ .n_adchan = 32,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_622x,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6225] = {
+ .name = "pci-6225",
+ .n_adchan = 80,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXI6225] = {
+ .name = "pxi-6225",
+ .n_adchan = 80,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6229] = {
+ .name = "pci-6229",
+ .n_adchan = 32,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .n_aochan = 4,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6250] = {
+ .name = "pci-6250",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .reg_type = ni_reg_625x,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6251] = {
+ .name = "pci-6251",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_625x_ao,
+ .reg_type = ni_reg_625x,
+ .ao_speed = 350,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCIE6251] = {
+ .name = "pcie-6251",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_625x_ao,
+ .reg_type = ni_reg_625x,
+ .ao_speed = 350,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXIE6251] = {
+ .name = "pxie-6251",
+ .n_adchan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_625x_ao,
+ .reg_type = ni_reg_625x,
+ .ao_speed = 350,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6254] = {
+ .name = "pci-6254",
+ .n_adchan = 32,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .reg_type = ni_reg_625x,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6259] = {
+ .name = "pci-6259",
+ .n_adchan = 32,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .n_aochan = 4,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_625x_ao,
+ .reg_type = ni_reg_625x,
+ .ao_speed = 350,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCIE6259] = {
+ .name = "pcie-6259",
+ .n_adchan = 32,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .n_aochan = 4,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_625x_ao,
+ .reg_type = ni_reg_625x,
+ .ao_speed = 350,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6280] = {
+ .name = "pci-6280",
+ .n_adchan = 16,
+ .ai_maxdata = 0x3ffff,
+ .ai_fifo_depth = 2047,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 1600,
+ .ao_fifo_depth = 8191,
+ .reg_type = ni_reg_628x,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6281] = {
+ .name = "pci-6281",
+ .n_adchan = 16,
+ .ai_maxdata = 0x3ffff,
+ .ai_fifo_depth = 2047,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 1600,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_628x_ao,
+ .reg_type = ni_reg_628x,
+ .ao_speed = 350,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXI6281] = {
+ .name = "pxi-6281",
+ .n_adchan = 16,
+ .ai_maxdata = 0x3ffff,
+ .ai_fifo_depth = 2047,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 1600,
+ .n_aochan = 2,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_628x_ao,
+ .reg_type = ni_reg_628x,
+ .ao_speed = 350,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6284] = {
+ .name = "pci-6284",
+ .n_adchan = 32,
+ .ai_maxdata = 0x3ffff,
+ .ai_fifo_depth = 2047,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 1600,
+ .reg_type = ni_reg_628x,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6289] = {
+ .name = "pci-6289",
+ .n_adchan = 32,
+ .ai_maxdata = 0x3ffff,
+ .ai_fifo_depth = 2047,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 1600,
+ .n_aochan = 4,
+ .ao_maxdata = 0xffff,
+ .ao_fifo_depth = 8191,
+ .ao_range_table = &range_ni_M_628x_ao,
+ .reg_type = ni_reg_628x,
+ .ao_speed = 350,
+ .has_32dio_chan = 1,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6143] = {
+ .name = "pci-6143",
+ .n_adchan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_6143,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_6143,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+ [BOARD_PXI6143] = {
+ .name = "pxi-6143",
+ .n_adchan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_6143,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_6143,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+};
+
+#include "ni_mio_common.c"
+
+static int pcimio_ai_change(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->ai_mite_ring, s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_ao_change(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->ao_mite_ring, s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_gpct0_change(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->gpct_mite_ring[0], s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_gpct1_change(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->gpct_mite_ring[1], s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_dio_change(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->cdo_mite_ring, s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void m_series_init_eeprom_buffer(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ static const int Start_Cal_EEPROM = 0x400;
+ static const unsigned window_size = 10;
+ static const int serial_number_eeprom_offset = 0x4;
+ static const int serial_number_eeprom_length = 0x4;
+ unsigned old_iodwbsr_bits;
+ unsigned old_iodwbsr1_bits;
+ unsigned old_iodwcr1_bits;
+ int i;
+
+ old_iodwbsr_bits = readl(devpriv->mite->mite_io_addr + MITE_IODWBSR);
+ old_iodwbsr1_bits = readl(devpriv->mite->mite_io_addr + MITE_IODWBSR_1);
+ old_iodwcr1_bits = readl(devpriv->mite->mite_io_addr + MITE_IODWCR_1);
+ writel(0x0, devpriv->mite->mite_io_addr + MITE_IODWBSR);
+ writel(((0x80 | window_size) | devpriv->mite->daq_phys_addr),
+ devpriv->mite->mite_io_addr + MITE_IODWBSR_1);
+ writel(0x1 | old_iodwcr1_bits,
+ devpriv->mite->mite_io_addr + MITE_IODWCR_1);
+ writel(0xf, devpriv->mite->mite_io_addr + 0x30);
+
+ BUG_ON(serial_number_eeprom_length > sizeof(devpriv->serial_number));
+ for (i = 0; i < serial_number_eeprom_length; ++i) {
+ char *byte_ptr = (char *)&devpriv->serial_number + i;
+ *byte_ptr = ni_readb(dev, serial_number_eeprom_offset + i);
+ }
+ devpriv->serial_number = be32_to_cpu(devpriv->serial_number);
+
+ for (i = 0; i < M_SERIES_EEPROM_SIZE; ++i)
+ devpriv->eeprom_buffer[i] = ni_readb(dev, Start_Cal_EEPROM + i);
+
+ writel(old_iodwbsr1_bits, devpriv->mite->mite_io_addr + MITE_IODWBSR_1);
+ writel(old_iodwbsr_bits, devpriv->mite->mite_io_addr + MITE_IODWBSR);
+ writel(old_iodwcr1_bits, devpriv->mite->mite_io_addr + MITE_IODWCR_1);
+ writel(0x0, devpriv->mite->mite_io_addr + 0x30);
+}
+
+static void init_6143(struct comedi_device *dev)
+{
+ const struct ni_board_struct *board = dev->board_ptr;
+ struct ni_private *devpriv = dev->private;
+
+ /* Disable interrupts */
+ ni_stc_writew(dev, 0, Interrupt_Control_Register);
+
+ /* Initialise 6143 AI specific bits */
+
+ /* Set G0,G1 DMA mode to E series version */
+ ni_writeb(dev, 0x00, Magic_6143);
+ /* Set EOCMode, ADCMode and pipelinedelay */
+ ni_writeb(dev, 0x80, PipelineDelay_6143);
+ /* Set EOC Delay */
+ ni_writeb(dev, 0x00, EOC_Set_6143);
+
+ /* Set the FIFO half full level */
+ ni_writel(dev, board->ai_fifo_depth / 2, AIFIFO_Flag_6143);
+
+ /* Strobe Relay disable bit */
+ devpriv->ai_calib_source_enabled = 0;
+ ni_writew(dev, devpriv->ai_calib_source |
+ Calibration_Channel_6143_RelayOff,
+ Calibration_Channel_6143);
+ ni_writew(dev, devpriv->ai_calib_source, Calibration_Channel_6143);
+}
+
+static void pcimio_detach(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+
+ mio_common_detach(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ mite_free_ring(devpriv->ai_mite_ring);
+ mite_free_ring(devpriv->ao_mite_ring);
+ mite_free_ring(devpriv->cdo_mite_ring);
+ mite_free_ring(devpriv->gpct_mite_ring[0]);
+ mite_free_ring(devpriv->gpct_mite_ring[1]);
+ mite_detach(devpriv->mite);
+ }
+ if (dev->mmio)
+ iounmap(dev->mmio);
+ comedi_pci_disable(dev);
+}
+
+static int pcimio_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_board_struct *board = NULL;
+ struct ni_private *devpriv;
+ unsigned int irq;
+ int ret;
+
+ if (context < ARRAY_SIZE(ni_boards))
+ board = &ni_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = ni_alloc_private(dev);
+ if (ret)
+ return ret;
+ devpriv = dev->private;
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ if (board->reg_type & ni_reg_m_series_mask)
+ devpriv->is_m_series = 1;
+ if (board->reg_type & ni_reg_6xxx_mask)
+ devpriv->is_6xxx = 1;
+ if (board->reg_type == ni_reg_611x)
+ devpriv->is_611x = 1;
+ if (board->reg_type == ni_reg_6143)
+ devpriv->is_6143 = 1;
+ if (board->reg_type == ni_reg_622x)
+ devpriv->is_622x = 1;
+ if (board->reg_type == ni_reg_625x)
+ devpriv->is_625x = 1;
+ if (board->reg_type == ni_reg_628x)
+ devpriv->is_628x = 1;
+ if (board->reg_type & ni_reg_67xx_mask)
+ devpriv->is_67xx = 1;
+ if (board->reg_type == ni_reg_6711)
+ devpriv->is_6711 = 1;
+ if (board->reg_type == ni_reg_6713)
+ devpriv->is_6713 = 1;
+
+ ret = mite_setup(dev, devpriv->mite);
+ if (ret < 0)
+ return ret;
+
+ devpriv->ai_mite_ring = mite_alloc_ring(devpriv->mite);
+ if (!devpriv->ai_mite_ring)
+ return -ENOMEM;
+ devpriv->ao_mite_ring = mite_alloc_ring(devpriv->mite);
+ if (!devpriv->ao_mite_ring)
+ return -ENOMEM;
+ devpriv->cdo_mite_ring = mite_alloc_ring(devpriv->mite);
+ if (!devpriv->cdo_mite_ring)
+ return -ENOMEM;
+ devpriv->gpct_mite_ring[0] = mite_alloc_ring(devpriv->mite);
+ if (!devpriv->gpct_mite_ring[0])
+ return -ENOMEM;
+ devpriv->gpct_mite_ring[1] = mite_alloc_ring(devpriv->mite);
+ if (!devpriv->gpct_mite_ring[1])
+ return -ENOMEM;
+
+ if (devpriv->is_m_series)
+ m_series_init_eeprom_buffer(dev);
+ if (devpriv->is_6143)
+ init_6143(dev);
+
+ irq = pcidev->irq;
+ if (irq) {
+ ret = request_irq(irq, ni_E_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ ret = ni_E_init(dev, 0, 1);
+ if (ret < 0)
+ return ret;
+
+ dev->subdevices[NI_AI_SUBDEV].buf_change = &pcimio_ai_change;
+ dev->subdevices[NI_AO_SUBDEV].buf_change = &pcimio_ao_change;
+ dev->subdevices[NI_GPCT_SUBDEV(0)].buf_change = &pcimio_gpct0_change;
+ dev->subdevices[NI_GPCT_SUBDEV(1)].buf_change = &pcimio_gpct1_change;
+ dev->subdevices[NI_DIO_SUBDEV].buf_change = &pcimio_dio_change;
+
+ return 0;
+}
+
+static struct comedi_driver ni_pcimio_driver = {
+ .driver_name = "ni_pcimio",
+ .module = THIS_MODULE,
+ .auto_attach = pcimio_auto_attach,
+ .detach = pcimio_detach,
+};
+
+static int ni_pcimio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_pcimio_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_pcimio_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x0162), BOARD_PCIMIO_16XE_50 }, /* 0x1620? */
+ { PCI_VDEVICE(NI, 0x1170), BOARD_PCIMIO_16XE_10 },
+ { PCI_VDEVICE(NI, 0x1180), BOARD_PCIMIO_16E_1 },
+ { PCI_VDEVICE(NI, 0x1190), BOARD_PCIMIO_16E_4 },
+ { PCI_VDEVICE(NI, 0x11b0), BOARD_PXI6070E },
+ { PCI_VDEVICE(NI, 0x11c0), BOARD_PXI6040E },
+ { PCI_VDEVICE(NI, 0x11d0), BOARD_PXI6030E },
+ { PCI_VDEVICE(NI, 0x1270), BOARD_PCI6032E },
+ { PCI_VDEVICE(NI, 0x1330), BOARD_PCI6031E },
+ { PCI_VDEVICE(NI, 0x1340), BOARD_PCI6033E },
+ { PCI_VDEVICE(NI, 0x1350), BOARD_PCI6071E },
+ { PCI_VDEVICE(NI, 0x14e0), BOARD_PCI6110 },
+ { PCI_VDEVICE(NI, 0x14f0), BOARD_PCI6111 },
+ { PCI_VDEVICE(NI, 0x1580), BOARD_PXI6031E },
+ { PCI_VDEVICE(NI, 0x15b0), BOARD_PXI6071E },
+ { PCI_VDEVICE(NI, 0x1880), BOARD_PCI6711 },
+ { PCI_VDEVICE(NI, 0x1870), BOARD_PCI6713 },
+ { PCI_VDEVICE(NI, 0x18b0), BOARD_PCI6052E },
+ { PCI_VDEVICE(NI, 0x18c0), BOARD_PXI6052E },
+ { PCI_VDEVICE(NI, 0x2410), BOARD_PCI6733 },
+ { PCI_VDEVICE(NI, 0x2420), BOARD_PXI6733 },
+ { PCI_VDEVICE(NI, 0x2430), BOARD_PCI6731 },
+ { PCI_VDEVICE(NI, 0x2890), BOARD_PCI6036E },
+ { PCI_VDEVICE(NI, 0x28c0), BOARD_PCI6014 },
+ { PCI_VDEVICE(NI, 0x2a60), BOARD_PCI6023E },
+ { PCI_VDEVICE(NI, 0x2a70), BOARD_PCI6024E },
+ { PCI_VDEVICE(NI, 0x2a80), BOARD_PCI6025E },
+ { PCI_VDEVICE(NI, 0x2ab0), BOARD_PXI6025E },
+ { PCI_VDEVICE(NI, 0x2b80), BOARD_PXI6713 },
+ { PCI_VDEVICE(NI, 0x2b90), BOARD_PXI6711 },
+ { PCI_VDEVICE(NI, 0x2c80), BOARD_PCI6035E },
+ { PCI_VDEVICE(NI, 0x2ca0), BOARD_PCI6034E },
+ { PCI_VDEVICE(NI, 0x70aa), BOARD_PCI6229 },
+ { PCI_VDEVICE(NI, 0x70ab), BOARD_PCI6259 },
+ { PCI_VDEVICE(NI, 0x70ac), BOARD_PCI6289 },
+ { PCI_VDEVICE(NI, 0x70af), BOARD_PCI6221 },
+ { PCI_VDEVICE(NI, 0x70b0), BOARD_PCI6220 },
+ { PCI_VDEVICE(NI, 0x70b4), BOARD_PCI6250 },
+ { PCI_VDEVICE(NI, 0x70b6), BOARD_PCI6280 },
+ { PCI_VDEVICE(NI, 0x70b7), BOARD_PCI6254 },
+ { PCI_VDEVICE(NI, 0x70b8), BOARD_PCI6251 },
+ { PCI_VDEVICE(NI, 0x70bc), BOARD_PCI6284 },
+ { PCI_VDEVICE(NI, 0x70bd), BOARD_PCI6281 },
+ { PCI_VDEVICE(NI, 0x70bf), BOARD_PXI6281 },
+ { PCI_VDEVICE(NI, 0x70c0), BOARD_PCI6143 },
+ { PCI_VDEVICE(NI, 0x70f2), BOARD_PCI6224 },
+ { PCI_VDEVICE(NI, 0x70f3), BOARD_PXI6224 },
+ { PCI_VDEVICE(NI, 0x710d), BOARD_PXI6143 },
+ { PCI_VDEVICE(NI, 0x716c), BOARD_PCI6225 },
+ { PCI_VDEVICE(NI, 0x716d), BOARD_PXI6225 },
+ { PCI_VDEVICE(NI, 0x717f), BOARD_PCIE6259 },
+ { PCI_VDEVICE(NI, 0x71bc), BOARD_PCI6221_37PIN },
+ { PCI_VDEVICE(NI, 0x717d), BOARD_PCIE6251 },
+ { PCI_VDEVICE(NI, 0x72e8), BOARD_PXIE6251 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_pcimio_pci_table);
+
+static struct pci_driver ni_pcimio_pci_driver = {
+ .name = "ni_pcimio",
+ .id_table = ni_pcimio_pci_table,
+ .probe = ni_pcimio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_pcimio_driver, ni_pcimio_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_stc.h b/drivers/staging/comedi/drivers/ni_stc.h
new file mode 100644
index 000000000..bd69c3f0a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_stc.h
@@ -0,0 +1,1491 @@
+/*
+ module/ni_stc.h
+ Register descriptions for NI DAQ-STC chip
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998-9 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+ References:
+ DAQ-STC Technical Reference Manual
+*/
+
+#ifndef _COMEDI_NI_STC_H
+#define _COMEDI_NI_STC_H
+
+#include "ni_tio.h"
+
+#define _bit15 0x8000
+#define _bit14 0x4000
+#define _bit13 0x2000
+#define _bit12 0x1000
+#define _bit11 0x0800
+#define _bit10 0x0400
+#define _bit9 0x0200
+#define _bit8 0x0100
+#define _bit7 0x0080
+#define _bit6 0x0040
+#define _bit5 0x0020
+#define _bit4 0x0010
+#define _bit3 0x0008
+#define _bit2 0x0004
+#define _bit1 0x0002
+#define _bit0 0x0001
+
+#define NUM_PFI_OUTPUT_SELECT_REGS 6
+
+/* Registers in the National Instruments DAQ-STC chip */
+
+#define Interrupt_A_Ack_Register 2
+#define G0_Gate_Interrupt_Ack _bit15
+#define G0_TC_Interrupt_Ack _bit14
+#define AI_Error_Interrupt_Ack _bit13
+#define AI_STOP_Interrupt_Ack _bit12
+#define AI_START_Interrupt_Ack _bit11
+#define AI_START2_Interrupt_Ack _bit10
+#define AI_START1_Interrupt_Ack _bit9
+#define AI_SC_TC_Interrupt_Ack _bit8
+#define AI_SC_TC_Error_Confirm _bit7
+#define G0_TC_Error_Confirm _bit6
+#define G0_Gate_Error_Confirm _bit5
+
+#define AI_Status_1_Register 2
+#define Interrupt_A_St 0x8000
+#define AI_FIFO_Full_St 0x4000
+#define AI_FIFO_Half_Full_St 0x2000
+#define AI_FIFO_Empty_St 0x1000
+#define AI_Overrun_St 0x0800
+#define AI_Overflow_St 0x0400
+#define AI_SC_TC_Error_St 0x0200
+#define AI_START2_St 0x0100
+#define AI_START1_St 0x0080
+#define AI_SC_TC_St 0x0040
+#define AI_START_St 0x0020
+#define AI_STOP_St 0x0010
+#define G0_TC_St 0x0008
+#define G0_Gate_Interrupt_St 0x0004
+#define AI_FIFO_Request_St 0x0002
+#define Pass_Thru_0_Interrupt_St 0x0001
+
+#define AI_Status_2_Register 5
+
+#define Interrupt_B_Ack_Register 3
+enum Interrupt_B_Ack_Bits {
+ G1_Gate_Error_Confirm = _bit1,
+ G1_TC_Error_Confirm = _bit2,
+ AO_BC_TC_Trigger_Error_Confirm = _bit3,
+ AO_BC_TC_Error_Confirm = _bit4,
+ AO_UI2_TC_Error_Confrim = _bit5,
+ AO_UI2_TC_Interrupt_Ack = _bit6,
+ AO_UC_TC_Interrupt_Ack = _bit7,
+ AO_BC_TC_Interrupt_Ack = _bit8,
+ AO_START1_Interrupt_Ack = _bit9,
+ AO_UPDATE_Interrupt_Ack = _bit10,
+ AO_START_Interrupt_Ack = _bit11,
+ AO_STOP_Interrupt_Ack = _bit12,
+ AO_Error_Interrupt_Ack = _bit13,
+ G1_TC_Interrupt_Ack = _bit14,
+ G1_Gate_Interrupt_Ack = _bit15
+};
+
+#define AO_Status_1_Register 3
+#define Interrupt_B_St _bit15
+#define AO_FIFO_Full_St _bit14
+#define AO_FIFO_Half_Full_St _bit13
+#define AO_FIFO_Empty_St _bit12
+#define AO_BC_TC_Error_St _bit11
+#define AO_START_St _bit10
+#define AO_Overrun_St _bit9
+#define AO_START1_St _bit8
+#define AO_BC_TC_St _bit7
+#define AO_UC_TC_St _bit6
+#define AO_UPDATE_St _bit5
+#define AO_UI2_TC_St _bit4
+#define G1_TC_St _bit3
+#define G1_Gate_Interrupt_St _bit2
+#define AO_FIFO_Request_St _bit1
+#define Pass_Thru_1_Interrupt_St _bit0
+
+#define AI_Command_2_Register 4
+#define AI_End_On_SC_TC _bit15
+#define AI_End_On_End_Of_Scan _bit14
+#define AI_START1_Disable _bit11
+#define AI_SC_Save_Trace _bit10
+#define AI_SI_Switch_Load_On_SC_TC _bit9
+#define AI_SI_Switch_Load_On_STOP _bit8
+#define AI_SI_Switch_Load_On_TC _bit7
+#define AI_SC_Switch_Load_On_TC _bit4
+#define AI_STOP_Pulse _bit3
+#define AI_START_Pulse _bit2
+#define AI_START2_Pulse _bit1
+#define AI_START1_Pulse _bit0
+
+#define AO_Command_2_Register 5
+#define AO_End_On_BC_TC(x) (((x) & 0x3) << 14)
+#define AO_Start_Stop_Gate_Enable _bit13
+#define AO_UC_Save_Trace _bit12
+#define AO_BC_Gate_Enable _bit11
+#define AO_BC_Save_Trace _bit10
+#define AO_UI_Switch_Load_On_BC_TC _bit9
+#define AO_UI_Switch_Load_On_Stop _bit8
+#define AO_UI_Switch_Load_On_TC _bit7
+#define AO_UC_Switch_Load_On_BC_TC _bit6
+#define AO_UC_Switch_Load_On_TC _bit5
+#define AO_BC_Switch_Load_On_TC _bit4
+#define AO_Mute_B _bit3
+#define AO_Mute_A _bit2
+#define AO_UPDATE2_Pulse _bit1
+#define AO_START1_Pulse _bit0
+
+#define AO_Status_2_Register 6
+
+#define DIO_Parallel_Input_Register 7
+
+#define AI_Command_1_Register 8
+#define AI_Analog_Trigger_Reset _bit14
+#define AI_Disarm _bit13
+#define AI_SI2_Arm _bit12
+#define AI_SI2_Load _bit11
+#define AI_SI_Arm _bit10
+#define AI_SI_Load _bit9
+#define AI_DIV_Arm _bit8
+#define AI_DIV_Load _bit7
+#define AI_SC_Arm _bit6
+#define AI_SC_Load _bit5
+#define AI_SCAN_IN_PROG_Pulse _bit4
+#define AI_EXTMUX_CLK_Pulse _bit3
+#define AI_LOCALMUX_CLK_Pulse _bit2
+#define AI_SC_TC_Pulse _bit1
+#define AI_CONVERT_Pulse _bit0
+
+#define AO_Command_1_Register 9
+#define AO_Analog_Trigger_Reset _bit15
+#define AO_START_Pulse _bit14
+#define AO_Disarm _bit13
+#define AO_UI2_Arm_Disarm _bit12
+#define AO_UI2_Load _bit11
+#define AO_UI_Arm _bit10
+#define AO_UI_Load _bit9
+#define AO_UC_Arm _bit8
+#define AO_UC_Load _bit7
+#define AO_BC_Arm _bit6
+#define AO_BC_Load _bit5
+#define AO_DAC1_Update_Mode _bit4
+#define AO_LDAC1_Source_Select _bit3
+#define AO_DAC0_Update_Mode _bit2
+#define AO_LDAC0_Source_Select _bit1
+#define AO_UPDATE_Pulse _bit0
+
+#define DIO_Output_Register 10
+#define DIO_Parallel_Data_Out(a) ((a)&0xff)
+#define DIO_Parallel_Data_Mask 0xff
+#define DIO_SDOUT _bit0
+#define DIO_SDIN _bit4
+#define DIO_Serial_Data_Out(a) (((a)&0xff)<<8)
+#define DIO_Serial_Data_Mask 0xff00
+
+#define DIO_Control_Register 11
+#define DIO_Software_Serial_Control _bit11
+#define DIO_HW_Serial_Timebase _bit10
+#define DIO_HW_Serial_Enable _bit9
+#define DIO_HW_Serial_Start _bit8
+#define DIO_Pins_Dir(a) ((a)&0xff)
+#define DIO_Pins_Dir_Mask 0xff
+
+#define AI_Mode_1_Register 12
+#define AI_CONVERT_Source_Select(a) (((a) & 0x1f) << 11)
+#define AI_SI_Source_select(a) (((a) & 0x1f) << 6)
+#define AI_CONVERT_Source_Polarity _bit5
+#define AI_SI_Source_Polarity _bit4
+#define AI_Start_Stop _bit3
+#define AI_Mode_1_Reserved _bit2
+#define AI_Continuous _bit1
+#define AI_Trigger_Once _bit0
+
+#define AI_Mode_2_Register 13
+#define AI_SC_Gate_Enable _bit15
+#define AI_Start_Stop_Gate_Enable _bit14
+#define AI_Pre_Trigger _bit13
+#define AI_External_MUX_Present _bit12
+#define AI_SI2_Initial_Load_Source _bit9
+#define AI_SI2_Reload_Mode _bit8
+#define AI_SI_Initial_Load_Source _bit7
+#define AI_SI_Reload_Mode(a) (((a) & 0x7)<<4)
+#define AI_SI_Write_Switch _bit3
+#define AI_SC_Initial_Load_Source _bit2
+#define AI_SC_Reload_Mode _bit1
+#define AI_SC_Write_Switch _bit0
+
+#define AI_SI_Load_A_Registers 14
+#define AI_SI_Load_B_Registers 16
+#define AI_SC_Load_A_Registers 18
+#define AI_SC_Load_B_Registers 20
+#define AI_SI_Save_Registers 64
+#define AI_SC_Save_Registers 66
+
+#define AI_SI2_Load_A_Register 23
+#define AI_SI2_Load_B_Register 25
+
+#define Joint_Status_1_Register 27
+#define DIO_Serial_IO_In_Progress_St _bit12
+
+#define DIO_Serial_Input_Register 28
+#define Joint_Status_2_Register 29
+enum Joint_Status_2_Bits {
+ AO_TMRDACWRs_In_Progress_St = 0x20,
+};
+
+#define AO_Mode_1_Register 38
+#define AO_UPDATE_Source_Select(x) (((x)&0x1f)<<11)
+#define AO_UI_Source_Select(x) (((x)&0x1f)<<6)
+#define AO_Multiple_Channels _bit5
+#define AO_UPDATE_Source_Polarity _bit4
+#define AO_UI_Source_Polarity _bit3
+#define AO_UC_Switch_Load_Every_TC _bit2
+#define AO_Continuous _bit1
+#define AO_Trigger_Once _bit0
+
+#define AO_Mode_2_Register 39
+#define AO_FIFO_Mode_Mask (0x3 << 14)
+enum AO_FIFO_Mode_Bits {
+ AO_FIFO_Mode_HF_to_F = (3 << 14),
+ AO_FIFO_Mode_F = (2 << 14),
+ AO_FIFO_Mode_HF = (1 << 14),
+ AO_FIFO_Mode_E = (0 << 14),
+};
+#define AO_FIFO_Retransmit_Enable _bit13
+#define AO_START1_Disable _bit12
+#define AO_UC_Initial_Load_Source _bit11
+#define AO_UC_Write_Switch _bit10
+#define AO_UI2_Initial_Load_Source _bit9
+#define AO_UI2_Reload_Mode _bit8
+#define AO_UI_Initial_Load_Source _bit7
+#define AO_UI_Reload_Mode(x) (((x) & 0x7) << 4)
+#define AO_UI_Write_Switch _bit3
+#define AO_BC_Initial_Load_Source _bit2
+#define AO_BC_Reload_Mode _bit1
+#define AO_BC_Write_Switch _bit0
+
+#define AO_UI_Load_A_Register 40
+#define AO_UI_Load_A_Register_High 40
+#define AO_UI_Load_A_Register_Low 41
+#define AO_UI_Load_B_Register 42
+#define AO_UI_Save_Registers 16
+#define AO_BC_Load_A_Register 44
+#define AO_BC_Load_A_Register_High 44
+#define AO_BC_Load_A_Register_Low 45
+#define AO_BC_Load_B_Register 46
+#define AO_BC_Load_B_Register_High 46
+#define AO_BC_Load_B_Register_Low 47
+#define AO_BC_Save_Registers 18
+#define AO_UC_Load_A_Register 48
+#define AO_UC_Load_A_Register_High 48
+#define AO_UC_Load_A_Register_Low 49
+#define AO_UC_Load_B_Register 50
+#define AO_UC_Save_Registers 20
+
+#define Clock_and_FOUT_Register 56
+enum Clock_and_FOUT_bits {
+ FOUT_Enable = _bit15,
+ FOUT_Timebase_Select = _bit14,
+ DIO_Serial_Out_Divide_By_2 = _bit13,
+ Slow_Internal_Time_Divide_By_2 = _bit12,
+ Slow_Internal_Timebase = _bit11,
+ G_Source_Divide_By_2 = _bit10,
+ Clock_To_Board_Divide_By_2 = _bit9,
+ Clock_To_Board = _bit8,
+ AI_Output_Divide_By_2 = _bit7,
+ AI_Source_Divide_By_2 = _bit6,
+ AO_Output_Divide_By_2 = _bit5,
+ AO_Source_Divide_By_2 = _bit4,
+ FOUT_Divider_mask = 0xf
+};
+static inline unsigned FOUT_Divider(unsigned divider)
+{
+ return divider & FOUT_Divider_mask;
+}
+
+#define IO_Bidirection_Pin_Register 57
+#define RTSI_Trig_Direction_Register 58
+enum RTSI_Trig_Direction_Bits {
+ Drive_RTSI_Clock_Bit = 0x1,
+ Use_RTSI_Clock_Bit = 0x2,
+};
+static inline unsigned RTSI_Output_Bit(unsigned channel, int is_mseries)
+{
+ unsigned max_channel;
+ unsigned base_bit_shift;
+ if (is_mseries) {
+ base_bit_shift = 8;
+ max_channel = 7;
+ } else {
+ base_bit_shift = 9;
+ max_channel = 6;
+ }
+ if (channel > max_channel) {
+ pr_err("%s: bug, invalid RTSI_channel=%i\n", __func__, channel);
+ return 0;
+ }
+ return 1 << (base_bit_shift + channel);
+}
+
+#define Interrupt_Control_Register 59
+#define Interrupt_B_Enable _bit15
+#define Interrupt_B_Output_Select(x) ((x)<<12)
+#define Interrupt_A_Enable _bit11
+#define Interrupt_A_Output_Select(x) ((x)<<8)
+#define Pass_Thru_0_Interrupt_Polarity _bit3
+#define Pass_Thru_1_Interrupt_Polarity _bit2
+#define Interrupt_Output_On_3_Pins _bit1
+#define Interrupt_Output_Polarity _bit0
+
+#define AI_Output_Control_Register 60
+#define AI_START_Output_Select _bit10
+#define AI_SCAN_IN_PROG_Output_Select(x) (((x) & 0x3) << 8)
+#define AI_EXTMUX_CLK_Output_Select(x) (((x) & 0x3) << 6)
+#define AI_LOCALMUX_CLK_Output_Select(x) ((x)<<4)
+#define AI_SC_TC_Output_Select(x) ((x)<<2)
+enum ai_convert_output_selection {
+ AI_CONVERT_Output_High_Z = 0,
+ AI_CONVERT_Output_Ground = 1,
+ AI_CONVERT_Output_Enable_Low = 2,
+ AI_CONVERT_Output_Enable_High = 3
+};
+static unsigned AI_CONVERT_Output_Select(enum ai_convert_output_selection
+ selection)
+{
+ return selection & 0x3;
+}
+
+#define AI_START_STOP_Select_Register 62
+#define AI_START_Polarity _bit15
+#define AI_STOP_Polarity _bit14
+#define AI_STOP_Sync _bit13
+#define AI_STOP_Edge _bit12
+#define AI_STOP_Select(a) (((a) & 0x1f)<<7)
+#define AI_START_Sync _bit6
+#define AI_START_Edge _bit5
+#define AI_START_Select(a) ((a) & 0x1f)
+
+#define AI_Trigger_Select_Register 63
+#define AI_START1_Polarity _bit15
+#define AI_START2_Polarity _bit14
+#define AI_START2_Sync _bit13
+#define AI_START2_Edge _bit12
+#define AI_START2_Select(a) (((a) & 0x1f) << 7)
+#define AI_START1_Sync _bit6
+#define AI_START1_Edge _bit5
+#define AI_START1_Select(a) ((a) & 0x1f)
+
+#define AI_DIV_Load_A_Register 64
+
+#define AO_Start_Select_Register 66
+#define AO_UI2_Software_Gate _bit15
+#define AO_UI2_External_Gate_Polarity _bit14
+#define AO_START_Polarity _bit13
+#define AO_AOFREQ_Enable _bit12
+#define AO_UI2_External_Gate_Select(a) (((a) & 0x1f) << 7)
+#define AO_START_Sync _bit6
+#define AO_START_Edge _bit5
+#define AO_START_Select(a) ((a) & 0x1f)
+
+#define AO_Trigger_Select_Register 67
+#define AO_UI2_External_Gate_Enable _bit15
+#define AO_Delayed_START1 _bit14
+#define AO_START1_Polarity _bit13
+#define AO_UI2_Source_Polarity _bit12
+#define AO_UI2_Source_Select(x) (((x)&0x1f)<<7)
+#define AO_START1_Sync _bit6
+#define AO_START1_Edge _bit5
+#define AO_START1_Select(x) (((x)&0x1f)<<0)
+
+#define AO_Mode_3_Register 70
+#define AO_UI2_Switch_Load_Next_TC _bit13
+#define AO_UC_Switch_Load_Every_BC_TC _bit12
+#define AO_Trigger_Length _bit11
+#define AO_Stop_On_Overrun_Error _bit5
+#define AO_Stop_On_BC_TC_Trigger_Error _bit4
+#define AO_Stop_On_BC_TC_Error _bit3
+#define AO_Not_An_UPDATE _bit2
+#define AO_Software_Gate _bit1
+#define AO_Last_Gate_Disable _bit0 /* M Series only */
+
+#define Joint_Reset_Register 72
+#define Software_Reset _bit11
+#define AO_Configuration_End _bit9
+#define AI_Configuration_End _bit8
+#define AO_Configuration_Start _bit5
+#define AI_Configuration_Start _bit4
+#define G1_Reset _bit3
+#define G0_Reset _bit2
+#define AO_Reset _bit1
+#define AI_Reset _bit0
+
+#define Interrupt_A_Enable_Register 73
+#define Pass_Thru_0_Interrupt_Enable _bit9
+#define G0_Gate_Interrupt_Enable _bit8
+#define AI_FIFO_Interrupt_Enable _bit7
+#define G0_TC_Interrupt_Enable _bit6
+#define AI_Error_Interrupt_Enable _bit5
+#define AI_STOP_Interrupt_Enable _bit4
+#define AI_START_Interrupt_Enable _bit3
+#define AI_START2_Interrupt_Enable _bit2
+#define AI_START1_Interrupt_Enable _bit1
+#define AI_SC_TC_Interrupt_Enable _bit0
+
+#define Interrupt_B_Enable_Register 75
+#define Pass_Thru_1_Interrupt_Enable _bit11
+#define G1_Gate_Interrupt_Enable _bit10
+#define G1_TC_Interrupt_Enable _bit9
+#define AO_FIFO_Interrupt_Enable _bit8
+#define AO_UI2_TC_Interrupt_Enable _bit7
+#define AO_UC_TC_Interrupt_Enable _bit6
+#define AO_Error_Interrupt_Enable _bit5
+#define AO_STOP_Interrupt_Enable _bit4
+#define AO_START_Interrupt_Enable _bit3
+#define AO_UPDATE_Interrupt_Enable _bit2
+#define AO_START1_Interrupt_Enable _bit1
+#define AO_BC_TC_Interrupt_Enable _bit0
+
+#define Second_IRQ_A_Enable_Register 74
+enum Second_IRQ_A_Enable_Bits {
+ AI_SC_TC_Second_Irq_Enable = _bit0,
+ AI_START1_Second_Irq_Enable = _bit1,
+ AI_START2_Second_Irq_Enable = _bit2,
+ AI_START_Second_Irq_Enable = _bit3,
+ AI_STOP_Second_Irq_Enable = _bit4,
+ AI_Error_Second_Irq_Enable = _bit5,
+ G0_TC_Second_Irq_Enable = _bit6,
+ AI_FIFO_Second_Irq_Enable = _bit7,
+ G0_Gate_Second_Irq_Enable = _bit8,
+ Pass_Thru_0_Second_Irq_Enable = _bit9
+};
+
+#define Second_IRQ_B_Enable_Register 76
+enum Second_IRQ_B_Enable_Bits {
+ AO_BC_TC_Second_Irq_Enable = _bit0,
+ AO_START1_Second_Irq_Enable = _bit1,
+ AO_UPDATE_Second_Irq_Enable = _bit2,
+ AO_START_Second_Irq_Enable = _bit3,
+ AO_STOP_Second_Irq_Enable = _bit4,
+ AO_Error_Second_Irq_Enable = _bit5,
+ AO_UC_TC_Second_Irq_Enable = _bit6,
+ AO_UI2_TC_Second_Irq_Enable = _bit7,
+ AO_FIFO_Second_Irq_Enable = _bit8,
+ G1_TC_Second_Irq_Enable = _bit9,
+ G1_Gate_Second_Irq_Enable = _bit10,
+ Pass_Thru_1_Second_Irq_Enable = _bit11
+};
+
+#define AI_Personal_Register 77
+#define AI_SHIFTIN_Pulse_Width _bit15
+#define AI_EOC_Polarity _bit14
+#define AI_SOC_Polarity _bit13
+#define AI_SHIFTIN_Polarity _bit12
+#define AI_CONVERT_Pulse_Timebase _bit11
+#define AI_CONVERT_Pulse_Width _bit10
+#define AI_CONVERT_Original_Pulse _bit9
+#define AI_FIFO_Flags_Polarity _bit8
+#define AI_Overrun_Mode _bit7
+#define AI_EXTMUX_CLK_Pulse_Width _bit6
+#define AI_LOCALMUX_CLK_Pulse_Width _bit5
+#define AI_AIFREQ_Polarity _bit4
+
+#define AO_Personal_Register 78
+enum AO_Personal_Bits {
+ AO_Interval_Buffer_Mode = 1 << 3,
+ AO_BC_Source_Select = 1 << 4,
+ AO_UPDATE_Pulse_Width = 1 << 5,
+ AO_UPDATE_Pulse_Timebase = 1 << 6,
+ AO_UPDATE_Original_Pulse = 1 << 7,
+ AO_DMA_PIO_Control = 1 << 8, /* M Series: reserved */
+ AO_AOFREQ_Polarity = 1 << 9, /* M Series: reserved */
+ AO_FIFO_Enable = 1 << 10,
+ AO_FIFO_Flags_Polarity = 1 << 11, /* M Series: reserved */
+ AO_TMRDACWR_Pulse_Width = 1 << 12,
+ AO_Fast_CPU = 1 << 13, /* M Series: reserved */
+ AO_Number_Of_DAC_Packages = 1 << 14, /* 1 for "single" mode, 0 for "dual" */
+ AO_Multiple_DACS_Per_Package = 1 << 15 /* m-series only */
+};
+#define RTSI_Trig_A_Output_Register 79
+#define RTSI_Trig_B_Output_Register 80
+enum RTSI_Trig_B_Output_Bits {
+ RTSI_Sub_Selection_1_Bit = 0x8000 /* not for m-series */
+};
+static inline unsigned RTSI_Trig_Output_Bits(unsigned rtsi_channel,
+ unsigned source)
+{
+ return (source & 0xf) << ((rtsi_channel % 4) * 4);
+};
+
+static inline unsigned RTSI_Trig_Output_Mask(unsigned rtsi_channel)
+{
+ return 0xf << ((rtsi_channel % 4) * 4);
+};
+
+/* inverse to RTSI_Trig_Output_Bits() */
+static inline unsigned RTSI_Trig_Output_Source(unsigned rtsi_channel,
+ unsigned bits)
+{
+ return (bits >> ((rtsi_channel % 4) * 4)) & 0xf;
+};
+
+#define RTSI_Board_Register 81
+#define Write_Strobe_0_Register 82
+#define Write_Strobe_1_Register 83
+#define Write_Strobe_2_Register 84
+#define Write_Strobe_3_Register 85
+
+#define AO_Output_Control_Register 86
+#define AO_External_Gate_Enable _bit15
+#define AO_External_Gate_Select(x) (((x)&0x1f)<<10)
+#define AO_Number_Of_Channels(x) (((x)&0xf)<<6)
+#define AO_UPDATE2_Output_Select(x) (((x)&0x3)<<4)
+#define AO_External_Gate_Polarity _bit3
+#define AO_UPDATE2_Output_Toggle _bit2
+enum ao_update_output_selection {
+ AO_Update_Output_High_Z = 0,
+ AO_Update_Output_Ground = 1,
+ AO_Update_Output_Enable_Low = 2,
+ AO_Update_Output_Enable_High = 3
+};
+static unsigned AO_UPDATE_Output_Select(enum ao_update_output_selection
+ selection)
+{
+ return selection & 0x3;
+}
+
+#define AI_Mode_3_Register 87
+#define AI_Trigger_Length _bit15
+#define AI_Delay_START _bit14
+#define AI_Software_Gate _bit13
+#define AI_SI_Special_Trigger_Delay _bit12
+#define AI_SI2_Source_Select _bit11
+#define AI_Delayed_START2 _bit10
+#define AI_Delayed_START1 _bit9
+#define AI_External_Gate_Mode _bit8
+#define AI_FIFO_Mode_HF_to_E (3<<6)
+#define AI_FIFO_Mode_F (2<<6)
+#define AI_FIFO_Mode_HF (1<<6)
+#define AI_FIFO_Mode_NE (0<<6)
+#define AI_External_Gate_Polarity _bit5
+#define AI_External_Gate_Select(a) ((a) & 0x1f)
+
+#define G_Autoincrement_Register(a) (68+(a))
+#define G_Command_Register(a) (6+(a))
+#define G_HW_Save_Register(a) (8+(a)*2)
+#define G_HW_Save_Register_High(a) (8+(a)*2)
+#define G_HW_Save_Register_Low(a) (9+(a)*2)
+#define G_Input_Select_Register(a) (36+(a))
+#define G_Load_A_Register(a) (28+(a)*4)
+#define G_Load_A_Register_High(a) (28+(a)*4)
+#define G_Load_A_Register_Low(a) (29+(a)*4)
+#define G_Load_B_Register(a) (30+(a)*4)
+#define G_Load_B_Register_High(a) (30+(a)*4)
+#define G_Load_B_Register_Low(a) (31+(a)*4)
+#define G_Mode_Register(a) (26+(a))
+#define G_Save_Register(a) (12+(a)*2)
+#define G_Save_Register_High(a) (12+(a)*2)
+#define G_Save_Register_Low(a) (13+(a)*2)
+#define G_Status_Register 4
+#define Analog_Trigger_Etc_Register 61
+
+/* command register */
+#define G_Disarm_Copy _bit15 /* strobe */
+#define G_Save_Trace_Copy _bit14
+#define G_Arm_Copy _bit13 /* strobe */
+#define G_Bank_Switch_Start _bit10 /* strobe */
+#define G_Little_Big_Endian _bit9
+#define G_Synchronized_Gate _bit8
+#define G_Write_Switch _bit7
+#define G_Up_Down(a) (((a)&0x03)<<5)
+#define G_Disarm _bit4 /* strobe */
+#define G_Analog_Trigger_Reset _bit3 /* strobe */
+#define G_Save_Trace _bit1
+#define G_Arm _bit0 /* strobe */
+
+/*channel agnostic names for the command register #defines */
+#define G_Bank_Switch_Enable _bit12
+#define G_Bank_Switch_Mode _bit11
+#define G_Load _bit2 /* strobe */
+
+/* input select register */
+#define G_Gate_Select(a) (((a)&0x1f)<<7)
+#define G_Source_Select(a) (((a)&0x1f)<<2)
+#define G_Write_Acknowledges_Irq _bit1
+#define G_Read_Acknowledges_Irq _bit0
+
+/* same input select register, but with channel agnostic names */
+#define G_Source_Polarity _bit15
+#define G_Output_Polarity _bit14
+#define G_OR_Gate _bit13
+#define G_Gate_Select_Load_Source _bit12
+
+/* mode register */
+#define G_Loading_On_TC _bit12
+#define G_Output_Mode(a) (((a)&0x03)<<8)
+#define G_Trigger_Mode_For_Edge_Gate(a) (((a)&0x03)<<3)
+#define G_Gating_Mode(a) (((a)&0x03)<<0)
+
+/* same input mode register, but with channel agnostic names */
+#define G_Load_Source_Select _bit7
+#define G_Reload_Source_Switching _bit15
+#define G_Loading_On_Gate _bit14
+#define G_Gate_Polarity _bit13
+
+#define G_Counting_Once(a) (((a)&0x03)<<10)
+#define G_Stop_Mode(a) (((a)&0x03)<<5)
+#define G_Gate_On_Both_Edges _bit2
+
+/* G_Status_Register */
+#define G1_Gate_Error_St _bit15
+#define G0_Gate_Error_St _bit14
+#define G1_TC_Error_St _bit13
+#define G0_TC_Error_St _bit12
+#define G1_No_Load_Between_Gates_St _bit11
+#define G0_No_Load_Between_Gates_St _bit10
+#define G1_Armed_St _bit9
+#define G0_Armed_St _bit8
+#define G1_Stale_Data_St _bit7
+#define G0_Stale_Data_St _bit6
+#define G1_Next_Load_Source_St _bit5
+#define G0_Next_Load_Source_St _bit4
+#define G1_Counting_St _bit3
+#define G0_Counting_St _bit2
+#define G1_Save_St _bit1
+#define G0_Save_St _bit0
+
+/* general purpose counter timer */
+#define G_Autoincrement(a) ((a)<<0)
+
+/*Analog_Trigger_Etc_Register*/
+#define Analog_Trigger_Mode(x) ((x) & 0x7)
+#define Analog_Trigger_Enable _bit3
+#define Analog_Trigger_Drive _bit4
+#define GPFO_1_Output_Select _bit7
+#define GPFO_0_Output_Select(a) ((a)<<11)
+#define GPFO_0_Output_Enable _bit14
+#define GPFO_1_Output_Enable _bit15
+
+/* Additional windowed registers unique to E series */
+
+/* 16 bit registers shadowed from DAQ-STC */
+#define Window_Address 0x00
+#define Window_Data 0x02
+
+#define Configuration_Memory_Clear 82
+#define ADC_FIFO_Clear 83
+#define DAC_FIFO_Clear 84
+
+/* i/o port offsets */
+
+/* 8 bit registers */
+#define XXX_Status 0x01
+enum XXX_Status_Bits {
+ PROMOUT = 0x1,
+ AI_FIFO_LOWER_NOT_EMPTY = 0x8,
+};
+#define Serial_Command 0x0d
+#define Misc_Command 0x0f
+#define Port_A 0x19
+#define Port_B 0x1b
+#define Port_C 0x1d
+#define Configuration 0x1f
+#define Strobes 0x01
+#define Channel_A_Mode 0x03
+#define Channel_B_Mode 0x05
+#define Channel_C_Mode 0x07
+#define AI_AO_Select 0x09
+enum AI_AO_Select_Bits {
+ AI_DMA_Select_Shift = 0,
+ AI_DMA_Select_Mask = 0xf,
+ AO_DMA_Select_Shift = 4,
+ AO_DMA_Select_Mask = 0xf << AO_DMA_Select_Shift
+};
+#define G0_G1_Select 0x0b
+static inline unsigned ni_stc_dma_channel_select_bitfield(unsigned channel)
+{
+ if (channel < 4)
+ return 1 << channel;
+ if (channel == 4)
+ return 0x3;
+ if (channel == 5)
+ return 0x5;
+ BUG();
+ return 0;
+}
+
+static inline unsigned GPCT_DMA_Select_Bits(unsigned gpct_index,
+ unsigned mite_channel)
+{
+ BUG_ON(gpct_index > 1);
+ return ni_stc_dma_channel_select_bitfield(mite_channel) << (4 *
+ gpct_index);
+}
+
+static inline unsigned GPCT_DMA_Select_Mask(unsigned gpct_index)
+{
+ BUG_ON(gpct_index > 1);
+ return 0xf << (4 * gpct_index);
+}
+
+/* 16 bit registers */
+
+#define Configuration_Memory_Low 0x10
+enum Configuration_Memory_Low_Bits {
+ AI_DITHER = 0x200,
+ AI_LAST_CHANNEL = 0x8000,
+};
+#define Configuration_Memory_High 0x12
+enum Configuration_Memory_High_Bits {
+ AI_AC_COUPLE = 0x800,
+ AI_DIFFERENTIAL = 0x1000,
+ AI_COMMON = 0x2000,
+ AI_GROUND = 0x3000,
+};
+static inline unsigned int AI_CONFIG_CHANNEL(unsigned int channel)
+{
+ return channel & 0x3f;
+}
+
+#define ADC_FIFO_Data_Register 0x1c
+
+#define AO_Configuration 0x16
+#define AO_Bipolar _bit0
+#define AO_Deglitch _bit1
+#define AO_Ext_Ref _bit2
+#define AO_Ground_Ref _bit3
+#define AO_Channel(x) ((x) << 8)
+
+#define DAC_FIFO_Data 0x1e
+#define DAC0_Direct_Data 0x18
+#define DAC1_Direct_Data 0x1a
+
+/* 611x registers (these boards differ from the e-series) */
+
+#define Magic_611x 0x19 /* w8 (new) */
+#define Calibration_Channel_Select_611x 0x1a /* w16 (new) */
+#define ADC_FIFO_Data_611x 0x1c /* r32 (incompatible) */
+#define AI_FIFO_Offset_Load_611x 0x05 /* r8 (new) */
+#define DAC_FIFO_Data_611x 0x14 /* w32 (incompatible) */
+#define Cal_Gain_Select_611x 0x05 /* w8 (new) */
+
+#define AO_Window_Address_611x 0x18
+#define AO_Window_Data_611x 0x1e
+
+/* 6143 registers */
+#define Magic_6143 0x19 /* w8 */
+#define G0G1_DMA_Select_6143 0x0B /* w8 */
+#define PipelineDelay_6143 0x1f /* w8 */
+#define EOC_Set_6143 0x1D /* w8 */
+#define AIDMA_Select_6143 0x09 /* w8 */
+#define AIFIFO_Data_6143 0x8C /* w32 */
+#define AIFIFO_Flag_6143 0x84 /* w32 */
+#define AIFIFO_Control_6143 0x88 /* w32 */
+#define AIFIFO_Status_6143 0x88 /* w32 */
+#define AIFIFO_DMAThreshold_6143 0x90 /* w32 */
+#define AIFIFO_Words_Available_6143 0x94 /* w32 */
+
+#define Calibration_Channel_6143 0x42 /* w16 */
+#define Calibration_LowTime_6143 0x20 /* w16 */
+#define Calibration_HighTime_6143 0x22 /* w16 */
+#define Relay_Counter_Load_Val__6143 0x4C /* w32 */
+#define Signature_6143 0x50 /* w32 */
+#define Release_Date_6143 0x54 /* w32 */
+#define Release_Oldest_Date_6143 0x58 /* w32 */
+
+#define Calibration_Channel_6143_RelayOn 0x8000 /* Calibration relay switch On */
+#define Calibration_Channel_6143_RelayOff 0x4000 /* Calibration relay switch Off */
+#define Calibration_Channel_Gnd_Gnd 0x00 /* Offset Calibration */
+#define Calibration_Channel_2v5_Gnd 0x02 /* 2.5V Reference */
+#define Calibration_Channel_Pwm_Gnd 0x05 /* +/- 5V Self Cal */
+#define Calibration_Channel_2v5_Pwm 0x0a /* PWM Calibration */
+#define Calibration_Channel_Pwm_Pwm 0x0d /* CMRR */
+#define Calibration_Channel_Gnd_Pwm 0x0e /* PWM Calibration */
+
+/* 671x, 611x registers */
+
+/* 671xi, 611x windowed ao registers */
+enum windowed_regs_67xx_61xx {
+ AO_Immediate_671x = 0x11, /* W 16 */
+ AO_Timed_611x = 0x10, /* W 16 */
+ AO_FIFO_Offset_Load_611x = 0x13, /* W32 */
+ AO_Later_Single_Point_Updates = 0x14, /* W 16 */
+ AO_Waveform_Generation_611x = 0x15, /* W 16 */
+ AO_Misc_611x = 0x16, /* W 16 */
+ AO_Calibration_Channel_Select_67xx = 0x17, /* W 16 */
+ AO_Configuration_2_67xx = 0x18, /* W 16 */
+ CAL_ADC_Command_67xx = 0x19, /* W 8 */
+ CAL_ADC_Status_67xx = 0x1a, /* R 8 */
+ CAL_ADC_Data_67xx = 0x1b, /* R 16 */
+ CAL_ADC_Config_Data_High_Word_67xx = 0x1c, /* RW 16 */
+ CAL_ADC_Config_Data_Low_Word_67xx = 0x1d, /* RW 16 */
+};
+static inline unsigned int DACx_Direct_Data_671x(int channel)
+{
+ return channel;
+}
+
+enum AO_Misc_611x_Bits {
+ CLEAR_WG = 1,
+};
+enum cs5529_configuration_bits {
+ CSCFG_CAL_CONTROL_MASK = 0x7,
+ CSCFG_SELF_CAL_OFFSET = 0x1,
+ CSCFG_SELF_CAL_GAIN = 0x2,
+ CSCFG_SELF_CAL_OFFSET_GAIN = 0x3,
+ CSCFG_SYSTEM_CAL_OFFSET = 0x5,
+ CSCFG_SYSTEM_CAL_GAIN = 0x6,
+ CSCFG_DONE = 1 << 3,
+ CSCFG_POWER_SAVE_SELECT = 1 << 4,
+ CSCFG_PORT_MODE = 1 << 5,
+ CSCFG_RESET_VALID = 1 << 6,
+ CSCFG_RESET = 1 << 7,
+ CSCFG_UNIPOLAR = 1 << 12,
+ CSCFG_WORD_RATE_2180_CYCLES = 0x0 << 13,
+ CSCFG_WORD_RATE_1092_CYCLES = 0x1 << 13,
+ CSCFG_WORD_RATE_532_CYCLES = 0x2 << 13,
+ CSCFG_WORD_RATE_388_CYCLES = 0x3 << 13,
+ CSCFG_WORD_RATE_324_CYCLES = 0x4 << 13,
+ CSCFG_WORD_RATE_17444_CYCLES = 0x5 << 13,
+ CSCFG_WORD_RATE_8724_CYCLES = 0x6 << 13,
+ CSCFG_WORD_RATE_4364_CYCLES = 0x7 << 13,
+ CSCFG_WORD_RATE_MASK = 0x7 << 13,
+ CSCFG_LOW_POWER = 1 << 16,
+};
+static inline unsigned int CS5529_CONFIG_DOUT(int output)
+{
+ return 1 << (18 + output);
+}
+
+static inline unsigned int CS5529_CONFIG_AOUT(int output)
+{
+ return 1 << (22 + output);
+}
+
+enum cs5529_command_bits {
+ CSCMD_POWER_SAVE = 0x1,
+ CSCMD_REGISTER_SELECT_MASK = 0xe,
+ CSCMD_OFFSET_REGISTER = 0x0,
+ CSCMD_GAIN_REGISTER = 0x2,
+ CSCMD_CONFIG_REGISTER = 0x4,
+ CSCMD_READ = 0x10,
+ CSCMD_CONTINUOUS_CONVERSIONS = 0x20,
+ CSCMD_SINGLE_CONVERSION = 0x40,
+ CSCMD_COMMAND = 0x80,
+};
+enum cs5529_status_bits {
+ CSS_ADC_BUSY = 0x1,
+ CSS_OSC_DETECT = 0x2, /* indicates adc error */
+ CSS_OVERRANGE = 0x4,
+};
+#define SerDacLd(x) (0x08<<(x))
+
+/*
+ This is stuff unique to the NI E series drivers,
+ but I thought I'd put it here anyway.
+*/
+
+enum { ai_gain_16 =
+ 0, ai_gain_8, ai_gain_14, ai_gain_4, ai_gain_611x, ai_gain_622x,
+ ai_gain_628x, ai_gain_6143
+};
+enum caldac_enum { caldac_none = 0, mb88341, dac8800, dac8043, ad8522,
+ ad8804, ad8842, ad8804_debug
+};
+enum ni_reg_type {
+ ni_reg_normal = 0x0,
+ ni_reg_611x = 0x1,
+ ni_reg_6711 = 0x2,
+ ni_reg_6713 = 0x4,
+ ni_reg_67xx_mask = 0x6,
+ ni_reg_6xxx_mask = 0x7,
+ ni_reg_622x = 0x8,
+ ni_reg_625x = 0x10,
+ ni_reg_628x = 0x18,
+ ni_reg_m_series_mask = 0x18,
+ ni_reg_6143 = 0x20
+};
+
+static const struct comedi_lrange range_ni_E_ao_ext;
+
+enum m_series_register_offsets {
+ M_Offset_CDIO_DMA_Select = 0x7, /* write */
+ M_Offset_SCXI_Status = 0x7, /* read */
+ M_Offset_AI_AO_Select = 0x9, /* write, same offset as e-series */
+ M_Offset_SCXI_Serial_Data_In = 0x9, /* read */
+ M_Offset_G0_G1_Select = 0xb, /* write, same offset as e-series */
+ M_Offset_Misc_Command = 0xf,
+ M_Offset_SCXI_Serial_Data_Out = 0x11,
+ M_Offset_SCXI_Control = 0x13,
+ M_Offset_SCXI_Output_Enable = 0x15,
+ M_Offset_AI_FIFO_Data = 0x1c,
+ M_Offset_Static_Digital_Output = 0x24, /* write */
+ M_Offset_Static_Digital_Input = 0x24, /* read */
+ M_Offset_DIO_Direction = 0x28,
+ M_Offset_Cal_PWM = 0x40,
+ M_Offset_AI_Config_FIFO_Data = 0x5e,
+ M_Offset_Interrupt_C_Enable = 0x88, /* write */
+ M_Offset_Interrupt_C_Status = 0x88, /* read */
+ M_Offset_Analog_Trigger_Control = 0x8c,
+ M_Offset_AO_Serial_Interrupt_Enable = 0xa0,
+ M_Offset_AO_Serial_Interrupt_Ack = 0xa1, /* write */
+ M_Offset_AO_Serial_Interrupt_Status = 0xa1, /* read */
+ M_Offset_AO_Calibration = 0xa3,
+ M_Offset_AO_FIFO_Data = 0xa4,
+ M_Offset_PFI_Filter = 0xb0,
+ M_Offset_RTSI_Filter = 0xb4,
+ M_Offset_SCXI_Legacy_Compatibility = 0xbc,
+ M_Offset_Interrupt_A_Ack = 0x104, /* write */
+ M_Offset_AI_Status_1 = 0x104, /* read */
+ M_Offset_Interrupt_B_Ack = 0x106, /* write */
+ M_Offset_AO_Status_1 = 0x106, /* read */
+ M_Offset_AI_Command_2 = 0x108, /* write */
+ M_Offset_G01_Status = 0x108, /* read */
+ M_Offset_AO_Command_2 = 0x10a,
+ M_Offset_AO_Status_2 = 0x10c, /* read */
+ M_Offset_G0_Command = 0x10c, /* write */
+ M_Offset_G1_Command = 0x10e, /* write */
+ M_Offset_G0_HW_Save = 0x110,
+ M_Offset_G0_HW_Save_High = 0x110,
+ M_Offset_AI_Command_1 = 0x110,
+ M_Offset_G0_HW_Save_Low = 0x112,
+ M_Offset_AO_Command_1 = 0x112,
+ M_Offset_G1_HW_Save = 0x114,
+ M_Offset_G1_HW_Save_High = 0x114,
+ M_Offset_G1_HW_Save_Low = 0x116,
+ M_Offset_AI_Mode_1 = 0x118,
+ M_Offset_G0_Save = 0x118,
+ M_Offset_G0_Save_High = 0x118,
+ M_Offset_AI_Mode_2 = 0x11a,
+ M_Offset_G0_Save_Low = 0x11a,
+ M_Offset_AI_SI_Load_A = 0x11c,
+ M_Offset_G1_Save = 0x11c,
+ M_Offset_G1_Save_High = 0x11c,
+ M_Offset_G1_Save_Low = 0x11e,
+ M_Offset_AI_SI_Load_B = 0x120, /* write */
+ M_Offset_AO_UI_Save = 0x120, /* read */
+ M_Offset_AI_SC_Load_A = 0x124, /* write */
+ M_Offset_AO_BC_Save = 0x124, /* read */
+ M_Offset_AI_SC_Load_B = 0x128, /* write */
+ M_Offset_AO_UC_Save = 0x128, /* read */
+ M_Offset_AI_SI2_Load_A = 0x12c,
+ M_Offset_AI_SI2_Load_B = 0x130,
+ M_Offset_G0_Mode = 0x134,
+ M_Offset_G1_Mode = 0x136, /* write */
+ M_Offset_Joint_Status_1 = 0x136, /* read */
+ M_Offset_G0_Load_A = 0x138,
+ M_Offset_Joint_Status_2 = 0x13a,
+ M_Offset_G0_Load_B = 0x13c,
+ M_Offset_G1_Load_A = 0x140,
+ M_Offset_G1_Load_B = 0x144,
+ M_Offset_G0_Input_Select = 0x148,
+ M_Offset_G1_Input_Select = 0x14a,
+ M_Offset_AO_Mode_1 = 0x14c,
+ M_Offset_AO_Mode_2 = 0x14e,
+ M_Offset_AO_UI_Load_A = 0x150,
+ M_Offset_AO_UI_Load_B = 0x154,
+ M_Offset_AO_BC_Load_A = 0x158,
+ M_Offset_AO_BC_Load_B = 0x15c,
+ M_Offset_AO_UC_Load_A = 0x160,
+ M_Offset_AO_UC_Load_B = 0x164,
+ M_Offset_Clock_and_FOUT = 0x170,
+ M_Offset_IO_Bidirection_Pin = 0x172,
+ M_Offset_RTSI_Trig_Direction = 0x174,
+ M_Offset_Interrupt_Control = 0x176,
+ M_Offset_AI_Output_Control = 0x178,
+ M_Offset_Analog_Trigger_Etc = 0x17a,
+ M_Offset_AI_START_STOP_Select = 0x17c,
+ M_Offset_AI_Trigger_Select = 0x17e,
+ M_Offset_AI_SI_Save = 0x180, /* read */
+ M_Offset_AI_DIV_Load_A = 0x180, /* write */
+ M_Offset_AI_SC_Save = 0x184, /* read */
+ M_Offset_AO_Start_Select = 0x184, /* write */
+ M_Offset_AO_Trigger_Select = 0x186,
+ M_Offset_AO_Mode_3 = 0x18c,
+ M_Offset_G0_Autoincrement = 0x188,
+ M_Offset_G1_Autoincrement = 0x18a,
+ M_Offset_Joint_Reset = 0x190,
+ M_Offset_Interrupt_A_Enable = 0x192,
+ M_Offset_Interrupt_B_Enable = 0x196,
+ M_Offset_AI_Personal = 0x19a,
+ M_Offset_AO_Personal = 0x19c,
+ M_Offset_RTSI_Trig_A_Output = 0x19e,
+ M_Offset_RTSI_Trig_B_Output = 0x1a0,
+ M_Offset_RTSI_Shared_MUX = 0x1a2,
+ M_Offset_AO_Output_Control = 0x1ac,
+ M_Offset_AI_Mode_3 = 0x1ae,
+ M_Offset_Configuration_Memory_Clear = 0x1a4,
+ M_Offset_AI_FIFO_Clear = 0x1a6,
+ M_Offset_AO_FIFO_Clear = 0x1a8,
+ M_Offset_G0_Counting_Mode = 0x1b0,
+ M_Offset_G1_Counting_Mode = 0x1b2,
+ M_Offset_G0_Second_Gate = 0x1b4,
+ M_Offset_G1_Second_Gate = 0x1b6,
+ M_Offset_G0_DMA_Config = 0x1b8, /* write */
+ M_Offset_G0_DMA_Status = 0x1b8, /* read */
+ M_Offset_G1_DMA_Config = 0x1ba, /* write */
+ M_Offset_G1_DMA_Status = 0x1ba, /* read */
+ M_Offset_G0_MSeries_ABZ = 0x1c0,
+ M_Offset_G1_MSeries_ABZ = 0x1c2,
+ M_Offset_Clock_and_Fout2 = 0x1c4,
+ M_Offset_PLL_Control = 0x1c6,
+ M_Offset_PLL_Status = 0x1c8,
+ M_Offset_PFI_Output_Select_1 = 0x1d0,
+ M_Offset_PFI_Output_Select_2 = 0x1d2,
+ M_Offset_PFI_Output_Select_3 = 0x1d4,
+ M_Offset_PFI_Output_Select_4 = 0x1d6,
+ M_Offset_PFI_Output_Select_5 = 0x1d8,
+ M_Offset_PFI_Output_Select_6 = 0x1da,
+ M_Offset_PFI_DI = 0x1dc,
+ M_Offset_PFI_DO = 0x1de,
+ M_Offset_AI_Config_FIFO_Bypass = 0x218,
+ M_Offset_SCXI_DIO_Enable = 0x21c,
+ M_Offset_CDI_FIFO_Data = 0x220, /* read */
+ M_Offset_CDO_FIFO_Data = 0x220, /* write */
+ M_Offset_CDIO_Status = 0x224, /* read */
+ M_Offset_CDIO_Command = 0x224, /* write */
+ M_Offset_CDI_Mode = 0x228,
+ M_Offset_CDO_Mode = 0x22c,
+ M_Offset_CDI_Mask_Enable = 0x230,
+ M_Offset_CDO_Mask_Enable = 0x234,
+};
+static inline int M_Offset_AO_Waveform_Order(int channel)
+{
+ return 0xc2 + 0x4 * channel;
+};
+
+static inline int M_Offset_AO_Config_Bank(int channel)
+{
+ return 0xc3 + 0x4 * channel;
+};
+
+static inline int M_Offset_DAC_Direct_Data(int channel)
+{
+ return 0xc0 + 0x4 * channel;
+}
+
+static inline int M_Offset_Gen_PWM(int channel)
+{
+ return 0x44 + 0x2 * channel;
+}
+
+static inline int M_Offset_Static_AI_Control(int i)
+{
+ int offset[] = {
+ 0x64,
+ 0x261,
+ 0x262,
+ 0x263,
+ };
+ if (((unsigned)i) >= ARRAY_SIZE(offset)) {
+ pr_err("%s: invalid channel=%i\n", __func__, i);
+ return offset[0];
+ }
+ return offset[i];
+};
+
+static inline int M_Offset_AO_Reference_Attenuation(int channel)
+{
+ int offset[] = {
+ 0x264,
+ 0x265,
+ 0x266,
+ 0x267
+ };
+ if (((unsigned)channel) >= ARRAY_SIZE(offset)) {
+ pr_err("%s: invalid channel=%i\n", __func__, channel);
+ return offset[0];
+ }
+ return offset[channel];
+};
+
+static inline unsigned M_Offset_PFI_Output_Select(unsigned n)
+{
+ if (n < 1 || n > NUM_PFI_OUTPUT_SELECT_REGS) {
+ pr_err("%s: invalid pfi output select register=%i\n",
+ __func__, n);
+ return M_Offset_PFI_Output_Select_1;
+ }
+ return M_Offset_PFI_Output_Select_1 + (n - 1) * 2;
+}
+
+enum MSeries_AI_Config_FIFO_Data_Bits {
+ MSeries_AI_Config_Channel_Type_Mask = 0x7 << 6,
+ MSeries_AI_Config_Channel_Type_Calibration_Bits = 0x0,
+ MSeries_AI_Config_Channel_Type_Differential_Bits = 0x1 << 6,
+ MSeries_AI_Config_Channel_Type_Common_Ref_Bits = 0x2 << 6,
+ MSeries_AI_Config_Channel_Type_Ground_Ref_Bits = 0x3 << 6,
+ MSeries_AI_Config_Channel_Type_Aux_Bits = 0x5 << 6,
+ MSeries_AI_Config_Channel_Type_Ghost_Bits = 0x7 << 6,
+ MSeries_AI_Config_Polarity_Bit = 0x1000, /* 0 for 2's complement encoding */
+ MSeries_AI_Config_Dither_Bit = 0x2000,
+ MSeries_AI_Config_Last_Channel_Bit = 0x4000,
+};
+static inline unsigned MSeries_AI_Config_Channel_Bits(unsigned channel)
+{
+ return channel & 0xf;
+}
+
+static inline unsigned MSeries_AI_Config_Bank_Bits(enum ni_reg_type reg_type,
+ unsigned channel)
+{
+ unsigned bits = channel & 0x30;
+ if (reg_type == ni_reg_622x) {
+ if (channel & 0x40)
+ bits |= 0x400;
+ }
+ return bits;
+}
+
+static inline unsigned MSeries_AI_Config_Gain_Bits(unsigned range)
+{
+ return (range & 0x7) << 9;
+}
+
+enum MSeries_Clock_and_Fout2_Bits {
+ MSeries_PLL_In_Source_Select_RTSI0_Bits = 0xb,
+ MSeries_PLL_In_Source_Select_Star_Trigger_Bits = 0x14,
+ MSeries_PLL_In_Source_Select_RTSI7_Bits = 0x1b,
+ MSeries_PLL_In_Source_Select_PXI_Clock10 = 0x1d,
+ MSeries_PLL_In_Source_Select_Mask = 0x1f,
+ MSeries_Timebase1_Select_Bit = 0x20, /* use PLL for timebase 1 */
+ MSeries_Timebase3_Select_Bit = 0x40, /* use PLL for timebase 3 */
+ /* use 10MHz instead of 20MHz for RTSI clock frequency. Appears
+ to have no effect, at least on pxi-6281, which always uses
+ 20MHz rtsi clock frequency */
+ MSeries_RTSI_10MHz_Bit = 0x80
+};
+static inline unsigned MSeries_PLL_In_Source_Select_RTSI_Bits(unsigned
+ RTSI_channel)
+{
+ if (RTSI_channel > 7) {
+ pr_err("%s: bug, invalid RTSI_channel=%i\n", __func__,
+ RTSI_channel);
+ return 0;
+ }
+ if (RTSI_channel == 7)
+ return MSeries_PLL_In_Source_Select_RTSI7_Bits;
+ else
+ return MSeries_PLL_In_Source_Select_RTSI0_Bits + RTSI_channel;
+}
+
+enum MSeries_PLL_Control_Bits {
+ MSeries_PLL_Enable_Bit = 0x1000,
+ MSeries_PLL_VCO_Mode_200_325MHz_Bits = 0x0,
+ MSeries_PLL_VCO_Mode_175_225MHz_Bits = 0x2000,
+ MSeries_PLL_VCO_Mode_100_225MHz_Bits = 0x4000,
+ MSeries_PLL_VCO_Mode_75_150MHz_Bits = 0x6000,
+};
+static inline unsigned MSeries_PLL_Divisor_Bits(unsigned divisor)
+{
+ static const unsigned max_divisor = 0x10;
+ if (divisor < 1 || divisor > max_divisor) {
+ pr_err("%s: bug, invalid divisor=%i\n", __func__, divisor);
+ return 0;
+ }
+ return (divisor & 0xf) << 8;
+}
+
+static inline unsigned MSeries_PLL_Multiplier_Bits(unsigned multiplier)
+{
+ static const unsigned max_multiplier = 0x100;
+ if (multiplier < 1 || multiplier > max_multiplier) {
+ pr_err("%s: bug, invalid multiplier=%i\n", __func__,
+ multiplier);
+ return 0;
+ }
+ return multiplier & 0xff;
+}
+
+enum MSeries_PLL_Status {
+ MSeries_PLL_Locked_Bit = 0x1
+};
+
+enum MSeries_AI_Config_FIFO_Bypass_Bits {
+ MSeries_AI_Bypass_Channel_Mask = 0x7,
+ MSeries_AI_Bypass_Bank_Mask = 0x78,
+ MSeries_AI_Bypass_Cal_Sel_Pos_Mask = 0x380,
+ MSeries_AI_Bypass_Cal_Sel_Neg_Mask = 0x1c00,
+ MSeries_AI_Bypass_Mode_Mux_Mask = 0x6000,
+ MSeries_AO_Bypass_AO_Cal_Sel_Mask = 0x38000,
+ MSeries_AI_Bypass_Gain_Mask = 0x1c0000,
+ MSeries_AI_Bypass_Dither_Bit = 0x200000,
+ MSeries_AI_Bypass_Polarity_Bit = 0x400000, /* 0 for 2's complement encoding */
+ MSeries_AI_Bypass_Config_FIFO_Bit = 0x80000000
+};
+static inline unsigned MSeries_AI_Bypass_Cal_Sel_Pos_Bits(int
+ calibration_source)
+{
+ return (calibration_source << 7) & MSeries_AI_Bypass_Cal_Sel_Pos_Mask;
+}
+
+static inline unsigned MSeries_AI_Bypass_Cal_Sel_Neg_Bits(int
+ calibration_source)
+{
+ return (calibration_source << 10) & MSeries_AI_Bypass_Cal_Sel_Pos_Mask;
+}
+
+static inline unsigned MSeries_AI_Bypass_Gain_Bits(int gain)
+{
+ return (gain << 18) & MSeries_AI_Bypass_Gain_Mask;
+}
+
+enum MSeries_AO_Config_Bank_Bits {
+ MSeries_AO_DAC_Offset_Select_Mask = 0x7,
+ MSeries_AO_DAC_Offset_0V_Bits = 0x0,
+ MSeries_AO_DAC_Offset_5V_Bits = 0x1,
+ MSeries_AO_DAC_Reference_Mask = 0x38,
+ MSeries_AO_DAC_Reference_10V_Internal_Bits = 0x0,
+ MSeries_AO_DAC_Reference_5V_Internal_Bits = 0x8,
+ MSeries_AO_Update_Timed_Bit = 0x40,
+ MSeries_AO_Bipolar_Bit = 0x80 /* turns on 2's complement encoding */
+};
+
+enum MSeries_AO_Reference_Attenuation_Bits {
+ MSeries_Attenuate_x5_Bit = 0x1
+};
+
+static inline unsigned MSeries_Cal_PWM_High_Time_Bits(unsigned count)
+{
+ return (count << 16) & 0xffff0000;
+}
+
+static inline unsigned MSeries_Cal_PWM_Low_Time_Bits(unsigned count)
+{
+ return count & 0xffff;
+}
+
+static inline unsigned MSeries_PFI_Output_Select_Mask(unsigned channel)
+{
+ return 0x1f << (channel % 3) * 5;
+};
+
+static inline unsigned MSeries_PFI_Output_Select_Bits(unsigned channel,
+ unsigned source)
+{
+ return (source & 0x1f) << ((channel % 3) * 5);
+};
+
+/* inverse to MSeries_PFI_Output_Select_Bits */
+static inline unsigned MSeries_PFI_Output_Select_Source(unsigned channel,
+ unsigned bits)
+{
+ return (bits >> ((channel % 3) * 5)) & 0x1f;
+};
+
+static inline unsigned MSeries_PFI_Filter_Select_Mask(unsigned channel)
+{
+ return 0x3 << (channel * 2);
+}
+
+static inline unsigned MSeries_PFI_Filter_Select_Bits(unsigned channel,
+ unsigned filter)
+{
+ return (filter << (channel *
+ 2)) & MSeries_PFI_Filter_Select_Mask(channel);
+}
+
+enum CDIO_DMA_Select_Bits {
+ CDI_DMA_Select_Shift = 0,
+ CDI_DMA_Select_Mask = 0xf,
+ CDO_DMA_Select_Shift = 4,
+ CDO_DMA_Select_Mask = 0xf << CDO_DMA_Select_Shift
+};
+
+enum CDIO_Status_Bits {
+ CDO_FIFO_Empty_Bit = 0x1,
+ CDO_FIFO_Full_Bit = 0x2,
+ CDO_FIFO_Request_Bit = 0x4,
+ CDO_Overrun_Bit = 0x8,
+ CDO_Underflow_Bit = 0x10,
+ CDI_FIFO_Empty_Bit = 0x10000,
+ CDI_FIFO_Full_Bit = 0x20000,
+ CDI_FIFO_Request_Bit = 0x40000,
+ CDI_Overrun_Bit = 0x80000,
+ CDI_Overflow_Bit = 0x100000
+};
+
+enum CDIO_Command_Bits {
+ CDO_Disarm_Bit = 0x1,
+ CDO_Arm_Bit = 0x2,
+ CDI_Disarm_Bit = 0x4,
+ CDI_Arm_Bit = 0x8,
+ CDO_Reset_Bit = 0x10,
+ CDI_Reset_Bit = 0x20,
+ CDO_Error_Interrupt_Enable_Set_Bit = 0x40,
+ CDO_Error_Interrupt_Enable_Clear_Bit = 0x80,
+ CDI_Error_Interrupt_Enable_Set_Bit = 0x100,
+ CDI_Error_Interrupt_Enable_Clear_Bit = 0x200,
+ CDO_FIFO_Request_Interrupt_Enable_Set_Bit = 0x400,
+ CDO_FIFO_Request_Interrupt_Enable_Clear_Bit = 0x800,
+ CDI_FIFO_Request_Interrupt_Enable_Set_Bit = 0x1000,
+ CDI_FIFO_Request_Interrupt_Enable_Clear_Bit = 0x2000,
+ CDO_Error_Interrupt_Confirm_Bit = 0x4000,
+ CDI_Error_Interrupt_Confirm_Bit = 0x8000,
+ CDO_Empty_FIFO_Interrupt_Enable_Set_Bit = 0x10000,
+ CDO_Empty_FIFO_Interrupt_Enable_Clear_Bit = 0x20000,
+ CDO_SW_Update_Bit = 0x80000,
+ CDI_SW_Update_Bit = 0x100000
+};
+
+enum CDI_Mode_Bits {
+ CDI_Sample_Source_Select_Mask = 0x3f,
+ CDI_Halt_On_Error_Bit = 0x200,
+ CDI_Polarity_Bit = 0x400, /* sample clock on falling edge */
+ CDI_FIFO_Mode_Bit = 0x800, /* set for half full mode, clear for not empty mode */
+ CDI_Data_Lane_Mask = 0x3000, /* data lanes specify which dio channels map to byte or word accesses to the dio fifos */
+ CDI_Data_Lane_0_15_Bits = 0x0,
+ CDI_Data_Lane_16_31_Bits = 0x1000,
+ CDI_Data_Lane_0_7_Bits = 0x0,
+ CDI_Data_Lane_8_15_Bits = 0x1000,
+ CDI_Data_Lane_16_23_Bits = 0x2000,
+ CDI_Data_Lane_24_31_Bits = 0x3000
+};
+
+enum CDO_Mode_Bits {
+ CDO_Sample_Source_Select_Mask = 0x3f,
+ CDO_Retransmit_Bit = 0x100,
+ CDO_Halt_On_Error_Bit = 0x200,
+ CDO_Polarity_Bit = 0x400, /* sample clock on falling edge */
+ CDO_FIFO_Mode_Bit = 0x800, /* set for half full mode, clear for not full mode */
+ CDO_Data_Lane_Mask = 0x3000, /* data lanes specify which dio channels map to byte or word accesses to the dio fifos */
+ CDO_Data_Lane_0_15_Bits = 0x0,
+ CDO_Data_Lane_16_31_Bits = 0x1000,
+ CDO_Data_Lane_0_7_Bits = 0x0,
+ CDO_Data_Lane_8_15_Bits = 0x1000,
+ CDO_Data_Lane_16_23_Bits = 0x2000,
+ CDO_Data_Lane_24_31_Bits = 0x3000
+};
+
+enum Interrupt_C_Enable_Bits {
+ Interrupt_Group_C_Enable_Bit = 0x1
+};
+
+enum Interrupt_C_Status_Bits {
+ Interrupt_Group_C_Status_Bit = 0x1
+};
+
+#define M_SERIES_EEPROM_SIZE 1024
+
+struct ni_board_struct {
+ const char *name;
+ int device_id;
+ int isapnp_id;
+
+ int n_adchan;
+ unsigned int ai_maxdata;
+
+ int ai_fifo_depth;
+ unsigned int alwaysdither:1;
+ int gainlkup;
+ int ai_speed;
+
+ int n_aochan;
+ unsigned int ao_maxdata;
+ int ao_fifo_depth;
+ const struct comedi_lrange *ao_range_table;
+ unsigned ao_speed;
+
+ int reg_type;
+ unsigned int has_8255:1;
+ unsigned int has_32dio_chan:1;
+
+ enum caldac_enum caldac[3];
+};
+
+#define MAX_N_CALDACS 34
+#define MAX_N_AO_CHAN 8
+#define NUM_GPCT 2
+
+struct ni_private {
+ unsigned short dio_output;
+ unsigned short dio_control;
+ int aimode;
+ unsigned int ai_calib_source;
+ unsigned int ai_calib_source_enabled;
+ spinlock_t window_lock;
+ spinlock_t soft_reg_copy_lock;
+ spinlock_t mite_channel_lock;
+
+ int changain_state;
+ unsigned int changain_spec;
+
+ unsigned int caldac_maxdata_list[MAX_N_CALDACS];
+ unsigned short caldacs[MAX_N_CALDACS];
+
+ unsigned short ai_cmd2;
+
+ unsigned short ao_conf[MAX_N_AO_CHAN];
+ unsigned short ao_mode1;
+ unsigned short ao_mode2;
+ unsigned short ao_mode3;
+ unsigned short ao_cmd1;
+ unsigned short ao_cmd2;
+ unsigned short ao_trigger_select;
+
+ struct ni_gpct_device *counter_dev;
+ unsigned short an_trig_etc_reg;
+
+ unsigned ai_offset[512];
+
+ unsigned long serial_interval_ns;
+ unsigned char serial_hw_mode;
+ unsigned short clock_and_fout;
+ unsigned short clock_and_fout2;
+
+ unsigned short int_a_enable_reg;
+ unsigned short int_b_enable_reg;
+ unsigned short io_bidirection_pin_reg;
+ unsigned short rtsi_trig_direction_reg;
+ unsigned short rtsi_trig_a_output_reg;
+ unsigned short rtsi_trig_b_output_reg;
+ unsigned short pfi_output_select_reg[NUM_PFI_OUTPUT_SELECT_REGS];
+ unsigned short ai_ao_select_reg;
+ unsigned short g0_g1_select_reg;
+ unsigned short cdio_dma_select_reg;
+
+ unsigned clock_ns;
+ unsigned clock_source;
+
+ unsigned short pwm_up_count;
+ unsigned short pwm_down_count;
+
+ unsigned short ai_fifo_buffer[0x2000];
+ uint8_t eeprom_buffer[M_SERIES_EEPROM_SIZE];
+ __be32 serial_number;
+
+ struct mite_struct *mite;
+ struct mite_channel *ai_mite_chan;
+ struct mite_channel *ao_mite_chan;
+ struct mite_channel *cdo_mite_chan;
+ struct mite_dma_descriptor_ring *ai_mite_ring;
+ struct mite_dma_descriptor_ring *ao_mite_ring;
+ struct mite_dma_descriptor_ring *cdo_mite_ring;
+ struct mite_dma_descriptor_ring *gpct_mite_ring[NUM_GPCT];
+
+ /* ni_pcimio board type flags (based on the boardinfo reg_type) */
+ unsigned int is_m_series:1;
+ unsigned int is_6xxx:1;
+ unsigned int is_611x:1;
+ unsigned int is_6143:1;
+ unsigned int is_622x:1;
+ unsigned int is_625x:1;
+ unsigned int is_628x:1;
+ unsigned int is_67xx:1;
+ unsigned int is_6711:1;
+ unsigned int is_6713:1;
+};
+
+#endif /* _COMEDI_NI_STC_H */
diff --git a/drivers/staging/comedi/drivers/ni_tio.c b/drivers/staging/comedi/drivers/ni_tio.c
new file mode 100644
index 000000000..c20c51bef
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tio.c
@@ -0,0 +1,1436 @@
+/*
+ comedi/drivers/ni_tio.c
+ Support for NI general purpose counters
+
+ Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+ * Module: ni_tio
+ * Description: National Instruments general purpose counters
+ * Author: J.P. Mellor <jpmellor@rose-hulman.edu>,
+ * Herman.Bruyninckx@mech.kuleuven.ac.be,
+ * Wim.Meeussen@mech.kuleuven.ac.be,
+ * Klaas.Gadeyne@mech.kuleuven.ac.be,
+ * Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Updated: Thu Nov 16 09:50:32 EST 2006
+ * Status: works
+ *
+ * This module is not used directly by end-users. Rather, it
+ * is used by other drivers (for example ni_660x and ni_pcimio)
+ * to provide support for NI's general purpose counters. It was
+ * originally based on the counter code from ni_660x.c and
+ * ni_mio_common.c.
+ *
+ * References:
+ * DAQ 660x Register-Level Programmer Manual (NI 370505A-01)
+ * DAQ 6601/6602 User Manual (NI 322137B-01)
+ * 340934b.pdf DAQ-STC reference manual
+ */
+
+/*
+TODO:
+ Support use of both banks X and Y
+*/
+
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "ni_tio_internal.h"
+
+/*
+ * clock sources for ni e and m series boards,
+ * get bits with GI_SRC_SEL()
+ */
+#define NI_M_TIMEBASE_1_CLK 0x0 /* 20MHz */
+#define NI_M_PFI_CLK(x) (((x) < 10) ? (1 + (x)) : (0xb + (x)))
+#define NI_M_RTSI_CLK(x) (((x) == 7) ? 0x1b : (0xb + (x)))
+#define NI_M_TIMEBASE_2_CLK 0x12 /* 100KHz */
+#define NI_M_NEXT_TC_CLK 0x13
+#define NI_M_NEXT_GATE_CLK 0x14 /* Gi_Src_SubSelect=0 */
+#define NI_M_PXI_STAR_TRIGGER_CLK 0x14 /* Gi_Src_SubSelect=1 */
+#define NI_M_PXI10_CLK 0x1d
+#define NI_M_TIMEBASE_3_CLK 0x1e /* 80MHz, Gi_Src_SubSelect=0 */
+#define NI_M_ANALOG_TRIGGER_OUT_CLK 0x1e /* Gi_Src_SubSelect=1 */
+#define NI_M_LOGIC_LOW_CLK 0x1f
+#define NI_M_MAX_PFI_CHAN 15
+#define NI_M_MAX_RTSI_CHAN 7
+
+/*
+ * clock sources for ni_660x boards,
+ * get bits with GI_SRC_SEL()
+ */
+#define NI_660X_TIMEBASE_1_CLK 0x0 /* 20MHz */
+#define NI_660X_SRC_PIN_I_CLK 0x1
+#define NI_660X_SRC_PIN_CLK(x) (0x2 + (x))
+#define NI_660X_NEXT_GATE_CLK 0xa
+#define NI_660X_RTSI_CLK(x) (0xb + (x))
+#define NI_660X_TIMEBASE_2_CLK 0x12 /* 100KHz */
+#define NI_660X_NEXT_TC_CLK 0x13
+#define NI_660X_TIMEBASE_3_CLK 0x1e /* 80MHz */
+#define NI_660X_LOGIC_LOW_CLK 0x1f
+#define NI_660X_MAX_SRC_PIN 7
+#define NI_660X_MAX_RTSI_CHAN 6
+
+/* ni m series gate_select */
+#define NI_M_TIMESTAMP_MUX_GATE_SEL 0x0
+#define NI_M_PFI_GATE_SEL(x) (((x) < 10) ? (1 + (x)) : (0xb + (x)))
+#define NI_M_RTSI_GATE_SEL(x) (((x) == 7) ? 0x1b : (0xb + (x)))
+#define NI_M_AI_START2_GATE_SEL 0x12
+#define NI_M_PXI_STAR_TRIGGER_GATE_SEL 0x13
+#define NI_M_NEXT_OUT_GATE_SEL 0x14
+#define NI_M_AI_START1_GATE_SEL 0x1c
+#define NI_M_NEXT_SRC_GATE_SEL 0x1d
+#define NI_M_ANALOG_TRIG_OUT_GATE_SEL 0x1e
+#define NI_M_LOGIC_LOW_GATE_SEL 0x1f
+
+/* ni_660x gate select */
+#define NI_660X_SRC_PIN_I_GATE_SEL 0x0
+#define NI_660X_GATE_PIN_I_GATE_SEL 0x1
+#define NI_660X_PIN_GATE_SEL(x) (0x2 + (x))
+#define NI_660X_NEXT_SRC_GATE_SEL 0xa
+#define NI_660X_RTSI_GATE_SEL(x) (0xb + (x))
+#define NI_660X_NEXT_OUT_GATE_SEL 0x14
+#define NI_660X_LOGIC_LOW_GATE_SEL 0x1f
+#define NI_660X_MAX_GATE_PIN 7
+
+/* ni_660x second gate select */
+#define NI_660X_SRC_PIN_I_GATE2_SEL 0x0
+#define NI_660X_UD_PIN_I_GATE2_SEL 0x1
+#define NI_660X_UD_PIN_GATE2_SEL(x) (0x2 + (x))
+#define NI_660X_NEXT_SRC_GATE2_SEL 0xa
+#define NI_660X_RTSI_GATE2_SEL(x) (0xb + (x))
+#define NI_660X_NEXT_OUT_GATE2_SEL 0x14
+#define NI_660X_SELECTED_GATE2_SEL 0x1e
+#define NI_660X_LOGIC_LOW_GATE2_SEL 0x1f
+#define NI_660X_MAX_UP_DOWN_PIN 7
+
+static inline unsigned GI_ALT_SYNC(enum ni_gpct_variant variant)
+{
+ switch (variant) {
+ case ni_gpct_variant_e_series:
+ default:
+ return 0;
+ case ni_gpct_variant_m_series:
+ return GI_M_ALT_SYNC;
+ case ni_gpct_variant_660x:
+ return GI_660X_ALT_SYNC;
+ }
+}
+
+static inline unsigned GI_PRESCALE_X2(enum ni_gpct_variant variant)
+{
+ switch (variant) {
+ case ni_gpct_variant_e_series:
+ default:
+ return 0;
+ case ni_gpct_variant_m_series:
+ return GI_M_PRESCALE_X2;
+ case ni_gpct_variant_660x:
+ return GI_660X_PRESCALE_X2;
+ }
+}
+
+static inline unsigned GI_PRESCALE_X8(enum ni_gpct_variant variant)
+{
+ switch (variant) {
+ case ni_gpct_variant_e_series:
+ default:
+ return 0;
+ case ni_gpct_variant_m_series:
+ return GI_M_PRESCALE_X8;
+ case ni_gpct_variant_660x:
+ return GI_660X_PRESCALE_X8;
+ }
+}
+
+static inline unsigned GI_HW_ARM_SEL_MASK(enum ni_gpct_variant variant)
+{
+ switch (variant) {
+ case ni_gpct_variant_e_series:
+ default:
+ return 0;
+ case ni_gpct_variant_m_series:
+ return GI_M_HW_ARM_SEL_MASK;
+ case ni_gpct_variant_660x:
+ return GI_660X_HW_ARM_SEL_MASK;
+ }
+}
+
+static int ni_tio_has_gate2_registers(const struct ni_gpct_device *counter_dev)
+{
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_e_series:
+ default:
+ return 0;
+ case ni_gpct_variant_m_series:
+ case ni_gpct_variant_660x:
+ return 1;
+ }
+}
+
+static void ni_tio_reset_count_and_disarm(struct ni_gpct *counter)
+{
+ unsigned cidx = counter->counter_index;
+
+ write_register(counter, GI_RESET(cidx), NITIO_RESET_REG(cidx));
+}
+
+static uint64_t ni_tio_clock_period_ps(const struct ni_gpct *counter,
+ unsigned generic_clock_source)
+{
+ uint64_t clock_period_ps;
+
+ switch (generic_clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK) {
+ case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS:
+ clock_period_ps = 50000;
+ break;
+ case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS:
+ clock_period_ps = 10000000;
+ break;
+ case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS:
+ clock_period_ps = 12500;
+ break;
+ case NI_GPCT_PXI10_CLOCK_SRC_BITS:
+ clock_period_ps = 100000;
+ break;
+ default:
+ /*
+ * clock period is specified by user with prescaling
+ * already taken into account.
+ */
+ return counter->clock_period_ps;
+ }
+
+ switch (generic_clock_source & NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK) {
+ case NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS:
+ break;
+ case NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS:
+ clock_period_ps *= 2;
+ break;
+ case NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS:
+ clock_period_ps *= 8;
+ break;
+ default:
+ BUG();
+ break;
+ }
+ return clock_period_ps;
+}
+
+static unsigned ni_tio_clock_src_modifiers(const struct ni_gpct *counter)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ const unsigned counting_mode_bits =
+ ni_tio_get_soft_copy(counter, NITIO_CNT_MODE_REG(cidx));
+ unsigned bits = 0;
+
+ if (ni_tio_get_soft_copy(counter, NITIO_INPUT_SEL_REG(cidx)) &
+ GI_SRC_POL_INVERT)
+ bits |= NI_GPCT_INVERT_CLOCK_SRC_BIT;
+ if (counting_mode_bits & GI_PRESCALE_X2(counter_dev->variant))
+ bits |= NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS;
+ if (counting_mode_bits & GI_PRESCALE_X8(counter_dev->variant))
+ bits |= NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS;
+ return bits;
+}
+
+static unsigned ni_m_series_clock_src_select(const struct ni_gpct *counter)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+ unsigned clock_source = 0;
+ unsigned src;
+ unsigned i;
+
+ src = GI_BITS_TO_SRC(ni_tio_get_soft_copy(counter,
+ NITIO_INPUT_SEL_REG(cidx)));
+
+ switch (src) {
+ case NI_M_TIMEBASE_1_CLK:
+ clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS;
+ break;
+ case NI_M_TIMEBASE_2_CLK:
+ clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS;
+ break;
+ case NI_M_TIMEBASE_3_CLK:
+ if (counter_dev->regs[second_gate_reg] & GI_SRC_SUBSEL)
+ clock_source =
+ NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS;
+ else
+ clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS;
+ break;
+ case NI_M_LOGIC_LOW_CLK:
+ clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS;
+ break;
+ case NI_M_NEXT_GATE_CLK:
+ if (counter_dev->regs[second_gate_reg] & GI_SRC_SUBSEL)
+ clock_source = NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS;
+ else
+ clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS;
+ break;
+ case NI_M_PXI10_CLK:
+ clock_source = NI_GPCT_PXI10_CLOCK_SRC_BITS;
+ break;
+ case NI_M_NEXT_TC_CLK:
+ clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS;
+ break;
+ default:
+ for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) {
+ if (src == NI_M_RTSI_CLK(i)) {
+ clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i);
+ break;
+ }
+ }
+ if (i <= NI_M_MAX_RTSI_CHAN)
+ break;
+ for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) {
+ if (src == NI_M_PFI_CLK(i)) {
+ clock_source = NI_GPCT_PFI_CLOCK_SRC_BITS(i);
+ break;
+ }
+ }
+ if (i <= NI_M_MAX_PFI_CHAN)
+ break;
+ BUG();
+ break;
+ }
+ clock_source |= ni_tio_clock_src_modifiers(counter);
+ return clock_source;
+}
+
+static unsigned ni_660x_clock_src_select(const struct ni_gpct *counter)
+{
+ unsigned clock_source = 0;
+ unsigned cidx = counter->counter_index;
+ unsigned src;
+ unsigned i;
+
+ src = GI_BITS_TO_SRC(ni_tio_get_soft_copy(counter,
+ NITIO_INPUT_SEL_REG(cidx)));
+
+ switch (src) {
+ case NI_660X_TIMEBASE_1_CLK:
+ clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS;
+ break;
+ case NI_660X_TIMEBASE_2_CLK:
+ clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS;
+ break;
+ case NI_660X_TIMEBASE_3_CLK:
+ clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS;
+ break;
+ case NI_660X_LOGIC_LOW_CLK:
+ clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS;
+ break;
+ case NI_660X_SRC_PIN_I_CLK:
+ clock_source = NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS;
+ break;
+ case NI_660X_NEXT_GATE_CLK:
+ clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS;
+ break;
+ case NI_660X_NEXT_TC_CLK:
+ clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS;
+ break;
+ default:
+ for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) {
+ if (src == NI_660X_RTSI_CLK(i)) {
+ clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i);
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_RTSI_CHAN)
+ break;
+ for (i = 0; i <= NI_660X_MAX_SRC_PIN; ++i) {
+ if (src == NI_660X_SRC_PIN_CLK(i)) {
+ clock_source =
+ NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i);
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_SRC_PIN)
+ break;
+ BUG();
+ break;
+ }
+ clock_source |= ni_tio_clock_src_modifiers(counter);
+ return clock_source;
+}
+
+static unsigned ni_tio_generic_clock_src_select(const struct ni_gpct *counter)
+{
+ switch (counter->counter_dev->variant) {
+ case ni_gpct_variant_e_series:
+ case ni_gpct_variant_m_series:
+ default:
+ return ni_m_series_clock_src_select(counter);
+ case ni_gpct_variant_660x:
+ return ni_660x_clock_src_select(counter);
+ }
+}
+
+static void ni_tio_set_sync_mode(struct ni_gpct *counter, int force_alt_sync)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ const unsigned counting_mode_reg = NITIO_CNT_MODE_REG(cidx);
+ static const uint64_t min_normal_sync_period_ps = 25000;
+ unsigned mode;
+ uint64_t clock_period_ps;
+
+ if (ni_tio_counting_mode_registers_present(counter_dev) == 0)
+ return;
+
+ mode = ni_tio_get_soft_copy(counter, counting_mode_reg);
+ switch (mode & GI_CNT_MODE_MASK) {
+ case GI_CNT_MODE_QUADX1:
+ case GI_CNT_MODE_QUADX2:
+ case GI_CNT_MODE_QUADX4:
+ case GI_CNT_MODE_SYNC_SRC:
+ force_alt_sync = 1;
+ break;
+ default:
+ break;
+ }
+
+ clock_period_ps = ni_tio_clock_period_ps(counter,
+ ni_tio_generic_clock_src_select(counter));
+
+ /*
+ * It's not clear what we should do if clock_period is unknown, so we
+ * are not using the alt sync bit in that case, but allow the caller
+ * to decide by using the force_alt_sync parameter.
+ */
+ if (force_alt_sync ||
+ (clock_period_ps && clock_period_ps < min_normal_sync_period_ps)) {
+ ni_tio_set_bits(counter, counting_mode_reg,
+ GI_ALT_SYNC(counter_dev->variant),
+ GI_ALT_SYNC(counter_dev->variant));
+ } else {
+ ni_tio_set_bits(counter, counting_mode_reg,
+ GI_ALT_SYNC(counter_dev->variant),
+ 0x0);
+ }
+}
+
+static int ni_tio_set_counter_mode(struct ni_gpct *counter, unsigned mode)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned mode_reg_mask;
+ unsigned mode_reg_values;
+ unsigned input_select_bits = 0;
+ /* these bits map directly on to the mode register */
+ static const unsigned mode_reg_direct_mask =
+ NI_GPCT_GATE_ON_BOTH_EDGES_BIT | NI_GPCT_EDGE_GATE_MODE_MASK |
+ NI_GPCT_STOP_MODE_MASK | NI_GPCT_OUTPUT_MODE_MASK |
+ NI_GPCT_HARDWARE_DISARM_MASK | NI_GPCT_LOADING_ON_TC_BIT |
+ NI_GPCT_LOADING_ON_GATE_BIT | NI_GPCT_LOAD_B_SELECT_BIT;
+
+ mode_reg_mask = mode_reg_direct_mask | GI_RELOAD_SRC_SWITCHING;
+ mode_reg_values = mode & mode_reg_direct_mask;
+ switch (mode & NI_GPCT_RELOAD_SOURCE_MASK) {
+ case NI_GPCT_RELOAD_SOURCE_FIXED_BITS:
+ break;
+ case NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS:
+ mode_reg_values |= GI_RELOAD_SRC_SWITCHING;
+ break;
+ case NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS:
+ input_select_bits |= GI_GATE_SEL_LOAD_SRC;
+ mode_reg_mask |= GI_GATING_MODE_MASK;
+ mode_reg_values |= GI_LEVEL_GATING;
+ break;
+ default:
+ break;
+ }
+ ni_tio_set_bits(counter, NITIO_MODE_REG(cidx),
+ mode_reg_mask, mode_reg_values);
+
+ if (ni_tio_counting_mode_registers_present(counter_dev)) {
+ unsigned bits = 0;
+
+ bits |= GI_CNT_MODE(mode >> NI_GPCT_COUNTING_MODE_SHIFT);
+ bits |= GI_INDEX_PHASE((mode >> NI_GPCT_INDEX_PHASE_BITSHIFT));
+ if (mode & NI_GPCT_INDEX_ENABLE_BIT)
+ bits |= GI_INDEX_MODE;
+ ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx),
+ GI_CNT_MODE_MASK | GI_INDEX_PHASE_MASK |
+ GI_INDEX_MODE, bits);
+ ni_tio_set_sync_mode(counter, 0);
+ }
+
+ ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), GI_CNT_DIR_MASK,
+ GI_CNT_DIR(mode >> NI_GPCT_COUNTING_DIRECTION_SHIFT));
+
+ if (mode & NI_GPCT_OR_GATE_BIT)
+ input_select_bits |= GI_OR_GATE;
+ if (mode & NI_GPCT_INVERT_OUTPUT_BIT)
+ input_select_bits |= GI_OUTPUT_POL_INVERT;
+ ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx),
+ GI_GATE_SEL_LOAD_SRC | GI_OR_GATE |
+ GI_OUTPUT_POL_INVERT, input_select_bits);
+
+ return 0;
+}
+
+int ni_tio_arm(struct ni_gpct *counter, int arm, unsigned start_trigger)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned command_transient_bits = 0;
+
+ if (arm) {
+ switch (start_trigger) {
+ case NI_GPCT_ARM_IMMEDIATE:
+ command_transient_bits |= GI_ARM;
+ break;
+ case NI_GPCT_ARM_PAIRED_IMMEDIATE:
+ command_transient_bits |= GI_ARM | GI_ARM_COPY;
+ break;
+ default:
+ break;
+ }
+ if (ni_tio_counting_mode_registers_present(counter_dev)) {
+ unsigned bits = 0;
+ unsigned sel_mask;
+
+ sel_mask = GI_HW_ARM_SEL_MASK(counter_dev->variant);
+
+ switch (start_trigger) {
+ case NI_GPCT_ARM_IMMEDIATE:
+ case NI_GPCT_ARM_PAIRED_IMMEDIATE:
+ break;
+ default:
+ if (start_trigger & NI_GPCT_ARM_UNKNOWN) {
+ /*
+ * pass-through the least significant
+ * bits so we can figure out what
+ * select later
+ */
+ bits |= GI_HW_ARM_ENA |
+ (GI_HW_ARM_SEL(start_trigger) &
+ sel_mask);
+ } else {
+ return -EINVAL;
+ }
+ break;
+ }
+ ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx),
+ GI_HW_ARM_ENA | sel_mask, bits);
+ }
+ } else {
+ command_transient_bits |= GI_DISARM;
+ }
+ ni_tio_set_bits_transient(counter, NITIO_CMD_REG(cidx),
+ 0, 0, command_transient_bits);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ni_tio_arm);
+
+static unsigned ni_660x_clk_src(unsigned int clock_source)
+{
+ unsigned clk_src = clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK;
+ unsigned ni_660x_clock;
+ unsigned i;
+
+ switch (clk_src) {
+ case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS:
+ ni_660x_clock = NI_660X_TIMEBASE_1_CLK;
+ break;
+ case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS:
+ ni_660x_clock = NI_660X_TIMEBASE_2_CLK;
+ break;
+ case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS:
+ ni_660x_clock = NI_660X_TIMEBASE_3_CLK;
+ break;
+ case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS:
+ ni_660x_clock = NI_660X_LOGIC_LOW_CLK;
+ break;
+ case NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS:
+ ni_660x_clock = NI_660X_SRC_PIN_I_CLK;
+ break;
+ case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS:
+ ni_660x_clock = NI_660X_NEXT_GATE_CLK;
+ break;
+ case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS:
+ ni_660x_clock = NI_660X_NEXT_TC_CLK;
+ break;
+ default:
+ for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) {
+ if (clk_src == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) {
+ ni_660x_clock = NI_660X_RTSI_CLK(i);
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_RTSI_CHAN)
+ break;
+ for (i = 0; i <= NI_660X_MAX_SRC_PIN; ++i) {
+ if (clk_src == NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i)) {
+ ni_660x_clock = NI_660X_SRC_PIN_CLK(i);
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_SRC_PIN)
+ break;
+ ni_660x_clock = 0;
+ BUG();
+ break;
+ }
+ return GI_SRC_SEL(ni_660x_clock);
+}
+
+static unsigned ni_m_clk_src(unsigned int clock_source)
+{
+ unsigned clk_src = clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK;
+ unsigned ni_m_series_clock;
+ unsigned i;
+
+ switch (clk_src) {
+ case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_TIMEBASE_1_CLK;
+ break;
+ case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_TIMEBASE_2_CLK;
+ break;
+ case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_TIMEBASE_3_CLK;
+ break;
+ case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_LOGIC_LOW_CLK;
+ break;
+ case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_NEXT_GATE_CLK;
+ break;
+ case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_NEXT_TC_CLK;
+ break;
+ case NI_GPCT_PXI10_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_PXI10_CLK;
+ break;
+ case NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_PXI_STAR_TRIGGER_CLK;
+ break;
+ case NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS:
+ ni_m_series_clock = NI_M_ANALOG_TRIGGER_OUT_CLK;
+ break;
+ default:
+ for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) {
+ if (clk_src == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) {
+ ni_m_series_clock = NI_M_RTSI_CLK(i);
+ break;
+ }
+ }
+ if (i <= NI_M_MAX_RTSI_CHAN)
+ break;
+ for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) {
+ if (clk_src == NI_GPCT_PFI_CLOCK_SRC_BITS(i)) {
+ ni_m_series_clock = NI_M_PFI_CLK(i);
+ break;
+ }
+ }
+ if (i <= NI_M_MAX_PFI_CHAN)
+ break;
+ pr_err("invalid clock source 0x%lx\n",
+ (unsigned long)clock_source);
+ BUG();
+ ni_m_series_clock = 0;
+ break;
+ }
+ return GI_SRC_SEL(ni_m_series_clock);
+};
+
+static void ni_tio_set_source_subselect(struct ni_gpct *counter,
+ unsigned int clock_source)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+
+ if (counter_dev->variant != ni_gpct_variant_m_series)
+ return;
+ switch (clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK) {
+ /* Gi_Source_Subselect is zero */
+ case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS:
+ case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS:
+ counter_dev->regs[second_gate_reg] &= ~GI_SRC_SUBSEL;
+ break;
+ /* Gi_Source_Subselect is one */
+ case NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS:
+ case NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS:
+ counter_dev->regs[second_gate_reg] |= GI_SRC_SUBSEL;
+ break;
+ /* Gi_Source_Subselect doesn't matter */
+ default:
+ return;
+ }
+ write_register(counter, counter_dev->regs[second_gate_reg],
+ second_gate_reg);
+}
+
+static int ni_tio_set_clock_src(struct ni_gpct *counter,
+ unsigned int clock_source,
+ unsigned int period_ns)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned bits = 0;
+
+ /* FIXME: validate clock source */
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_660x:
+ bits |= ni_660x_clk_src(clock_source);
+ break;
+ case ni_gpct_variant_e_series:
+ case ni_gpct_variant_m_series:
+ default:
+ bits |= ni_m_clk_src(clock_source);
+ break;
+ }
+ if (clock_source & NI_GPCT_INVERT_CLOCK_SRC_BIT)
+ bits |= GI_SRC_POL_INVERT;
+ ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx),
+ GI_SRC_SEL_MASK | GI_SRC_POL_INVERT, bits);
+ ni_tio_set_source_subselect(counter, clock_source);
+
+ if (ni_tio_counting_mode_registers_present(counter_dev)) {
+ bits = 0;
+ switch (clock_source & NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK) {
+ case NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS:
+ break;
+ case NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS:
+ bits |= GI_PRESCALE_X2(counter_dev->variant);
+ break;
+ case NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS:
+ bits |= GI_PRESCALE_X8(counter_dev->variant);
+ break;
+ default:
+ return -EINVAL;
+ }
+ ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx),
+ GI_PRESCALE_X2(counter_dev->variant) |
+ GI_PRESCALE_X8(counter_dev->variant), bits);
+ }
+ counter->clock_period_ps = period_ns * 1000;
+ ni_tio_set_sync_mode(counter, 0);
+ return 0;
+}
+
+static void ni_tio_get_clock_src(struct ni_gpct *counter,
+ unsigned int *clock_source,
+ unsigned int *period_ns)
+{
+ uint64_t temp64;
+
+ *clock_source = ni_tio_generic_clock_src_select(counter);
+ temp64 = ni_tio_clock_period_ps(counter, *clock_source);
+ do_div(temp64, 1000); /* ps to ns */
+ *period_ns = temp64;
+}
+
+static int ni_660x_set_gate(struct ni_gpct *counter, unsigned int gate_source)
+{
+ unsigned int chan = CR_CHAN(gate_source);
+ unsigned cidx = counter->counter_index;
+ unsigned gate_sel;
+ unsigned i;
+
+ switch (chan) {
+ case NI_GPCT_NEXT_SOURCE_GATE_SELECT:
+ gate_sel = NI_660X_NEXT_SRC_GATE_SEL;
+ break;
+ case NI_GPCT_NEXT_OUT_GATE_SELECT:
+ case NI_GPCT_LOGIC_LOW_GATE_SELECT:
+ case NI_GPCT_SOURCE_PIN_i_GATE_SELECT:
+ case NI_GPCT_GATE_PIN_i_GATE_SELECT:
+ gate_sel = chan & 0x1f;
+ break;
+ default:
+ for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) {
+ if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) {
+ gate_sel = chan & 0x1f;
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_RTSI_CHAN)
+ break;
+ for (i = 0; i <= NI_660X_MAX_GATE_PIN; ++i) {
+ if (chan == NI_GPCT_GATE_PIN_GATE_SELECT(i)) {
+ gate_sel = chan & 0x1f;
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_GATE_PIN)
+ break;
+ return -EINVAL;
+ }
+ ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx),
+ GI_GATE_SEL_MASK, GI_GATE_SEL(gate_sel));
+ return 0;
+}
+
+static int ni_m_set_gate(struct ni_gpct *counter, unsigned int gate_source)
+{
+ unsigned int chan = CR_CHAN(gate_source);
+ unsigned cidx = counter->counter_index;
+ unsigned gate_sel;
+ unsigned i;
+
+ switch (chan) {
+ case NI_GPCT_TIMESTAMP_MUX_GATE_SELECT:
+ case NI_GPCT_AI_START2_GATE_SELECT:
+ case NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT:
+ case NI_GPCT_NEXT_OUT_GATE_SELECT:
+ case NI_GPCT_AI_START1_GATE_SELECT:
+ case NI_GPCT_NEXT_SOURCE_GATE_SELECT:
+ case NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT:
+ case NI_GPCT_LOGIC_LOW_GATE_SELECT:
+ gate_sel = chan & 0x1f;
+ break;
+ default:
+ for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) {
+ if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) {
+ gate_sel = chan & 0x1f;
+ break;
+ }
+ }
+ if (i <= NI_M_MAX_RTSI_CHAN)
+ break;
+ for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) {
+ if (chan == NI_GPCT_PFI_GATE_SELECT(i)) {
+ gate_sel = chan & 0x1f;
+ break;
+ }
+ }
+ if (i <= NI_M_MAX_PFI_CHAN)
+ break;
+ return -EINVAL;
+ }
+ ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx),
+ GI_GATE_SEL_MASK, GI_GATE_SEL(gate_sel));
+ return 0;
+}
+
+static int ni_660x_set_gate2(struct ni_gpct *counter, unsigned int gate_source)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned int chan = CR_CHAN(gate_source);
+ unsigned gate2_reg = NITIO_GATE2_REG(cidx);
+ unsigned gate2_sel;
+ unsigned i;
+
+ switch (chan) {
+ case NI_GPCT_SOURCE_PIN_i_GATE_SELECT:
+ case NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT:
+ case NI_GPCT_SELECTED_GATE_GATE_SELECT:
+ case NI_GPCT_NEXT_OUT_GATE_SELECT:
+ case NI_GPCT_LOGIC_LOW_GATE_SELECT:
+ gate2_sel = chan & 0x1f;
+ break;
+ case NI_GPCT_NEXT_SOURCE_GATE_SELECT:
+ gate2_sel = NI_660X_NEXT_SRC_GATE2_SEL;
+ break;
+ default:
+ for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) {
+ if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) {
+ gate2_sel = chan & 0x1f;
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_RTSI_CHAN)
+ break;
+ for (i = 0; i <= NI_660X_MAX_UP_DOWN_PIN; ++i) {
+ if (chan == NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i)) {
+ gate2_sel = chan & 0x1f;
+ break;
+ }
+ }
+ if (i <= NI_660X_MAX_UP_DOWN_PIN)
+ break;
+ return -EINVAL;
+ }
+ counter_dev->regs[gate2_reg] |= GI_GATE2_MODE;
+ counter_dev->regs[gate2_reg] &= ~GI_GATE2_SEL_MASK;
+ counter_dev->regs[gate2_reg] |= GI_GATE2_SEL(gate2_sel);
+ write_register(counter, counter_dev->regs[gate2_reg], gate2_reg);
+ return 0;
+}
+
+static int ni_m_set_gate2(struct ni_gpct *counter, unsigned int gate_source)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned int chan = CR_CHAN(gate_source);
+ unsigned gate2_reg = NITIO_GATE2_REG(cidx);
+ unsigned gate2_sel;
+
+ /*
+ * FIXME: We don't know what the m-series second gate codes are,
+ * so we'll just pass the bits through for now.
+ */
+ switch (chan) {
+ default:
+ gate2_sel = chan & 0x1f;
+ break;
+ }
+ counter_dev->regs[gate2_reg] |= GI_GATE2_MODE;
+ counter_dev->regs[gate2_reg] &= ~GI_GATE2_SEL_MASK;
+ counter_dev->regs[gate2_reg] |= GI_GATE2_SEL(gate2_sel);
+ write_register(counter, counter_dev->regs[gate2_reg], gate2_reg);
+ return 0;
+}
+
+int ni_tio_set_gate_src(struct ni_gpct *counter, unsigned gate_index,
+ unsigned int gate_source)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned int chan = CR_CHAN(gate_source);
+ unsigned gate2_reg = NITIO_GATE2_REG(cidx);
+ unsigned mode = 0;
+
+ switch (gate_index) {
+ case 0:
+ if (chan == NI_GPCT_DISABLED_GATE_SELECT) {
+ ni_tio_set_bits(counter, NITIO_MODE_REG(cidx),
+ GI_GATING_MODE_MASK,
+ GI_GATING_DISABLED);
+ return 0;
+ }
+ if (gate_source & CR_INVERT)
+ mode |= GI_GATE_POL_INVERT;
+ if (gate_source & CR_EDGE)
+ mode |= GI_RISING_EDGE_GATING;
+ else
+ mode |= GI_LEVEL_GATING;
+ ni_tio_set_bits(counter, NITIO_MODE_REG(cidx),
+ GI_GATE_POL_INVERT | GI_GATING_MODE_MASK,
+ mode);
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_e_series:
+ case ni_gpct_variant_m_series:
+ default:
+ return ni_m_set_gate(counter, gate_source);
+ case ni_gpct_variant_660x:
+ return ni_660x_set_gate(counter, gate_source);
+ }
+ break;
+ case 1:
+ if (!ni_tio_has_gate2_registers(counter_dev))
+ return -EINVAL;
+
+ if (chan == NI_GPCT_DISABLED_GATE_SELECT) {
+ counter_dev->regs[gate2_reg] &= ~GI_GATE2_MODE;
+ write_register(counter, counter_dev->regs[gate2_reg],
+ gate2_reg);
+ return 0;
+ }
+ if (gate_source & CR_INVERT)
+ counter_dev->regs[gate2_reg] |= GI_GATE2_POL_INVERT;
+ else
+ counter_dev->regs[gate2_reg] &= ~GI_GATE2_POL_INVERT;
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_m_series:
+ return ni_m_set_gate2(counter, gate_source);
+ case ni_gpct_variant_660x:
+ return ni_660x_set_gate2(counter, gate_source);
+ default:
+ BUG();
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ni_tio_set_gate_src);
+
+static int ni_tio_set_other_src(struct ni_gpct *counter, unsigned index,
+ unsigned int source)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned int abz_reg, shift, mask;
+
+ if (counter_dev->variant != ni_gpct_variant_m_series)
+ return -EINVAL;
+
+ abz_reg = NITIO_ABZ_REG(cidx);
+ switch (index) {
+ case NI_GPCT_SOURCE_ENCODER_A:
+ shift = 10;
+ break;
+ case NI_GPCT_SOURCE_ENCODER_B:
+ shift = 5;
+ break;
+ case NI_GPCT_SOURCE_ENCODER_Z:
+ shift = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ mask = 0x1f << shift;
+ if (source > 0x1f)
+ source = 0x1f; /* Disable gate */
+
+ counter_dev->regs[abz_reg] &= ~mask;
+ counter_dev->regs[abz_reg] |= (source << shift) & mask;
+ write_register(counter, counter_dev->regs[abz_reg], abz_reg);
+ return 0;
+}
+
+static unsigned ni_660x_gate_to_generic_gate(unsigned gate)
+{
+ unsigned i;
+
+ switch (gate) {
+ case NI_660X_SRC_PIN_I_GATE_SEL:
+ return NI_GPCT_SOURCE_PIN_i_GATE_SELECT;
+ case NI_660X_GATE_PIN_I_GATE_SEL:
+ return NI_GPCT_GATE_PIN_i_GATE_SELECT;
+ case NI_660X_NEXT_SRC_GATE_SEL:
+ return NI_GPCT_NEXT_SOURCE_GATE_SELECT;
+ case NI_660X_NEXT_OUT_GATE_SEL:
+ return NI_GPCT_NEXT_OUT_GATE_SELECT;
+ case NI_660X_LOGIC_LOW_GATE_SEL:
+ return NI_GPCT_LOGIC_LOW_GATE_SELECT;
+ default:
+ for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) {
+ if (gate == NI_660X_RTSI_GATE_SEL(i))
+ return NI_GPCT_RTSI_GATE_SELECT(i);
+ }
+ for (i = 0; i <= NI_660X_MAX_GATE_PIN; ++i) {
+ if (gate == NI_660X_PIN_GATE_SEL(i))
+ return NI_GPCT_GATE_PIN_GATE_SELECT(i);
+ }
+ BUG();
+ break;
+ }
+ return 0;
+};
+
+static unsigned ni_m_gate_to_generic_gate(unsigned gate)
+{
+ unsigned i;
+
+ switch (gate) {
+ case NI_M_TIMESTAMP_MUX_GATE_SEL:
+ return NI_GPCT_TIMESTAMP_MUX_GATE_SELECT;
+ case NI_M_AI_START2_GATE_SEL:
+ return NI_GPCT_AI_START2_GATE_SELECT;
+ case NI_M_PXI_STAR_TRIGGER_GATE_SEL:
+ return NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT;
+ case NI_M_NEXT_OUT_GATE_SEL:
+ return NI_GPCT_NEXT_OUT_GATE_SELECT;
+ case NI_M_AI_START1_GATE_SEL:
+ return NI_GPCT_AI_START1_GATE_SELECT;
+ case NI_M_NEXT_SRC_GATE_SEL:
+ return NI_GPCT_NEXT_SOURCE_GATE_SELECT;
+ case NI_M_ANALOG_TRIG_OUT_GATE_SEL:
+ return NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT;
+ case NI_M_LOGIC_LOW_GATE_SEL:
+ return NI_GPCT_LOGIC_LOW_GATE_SELECT;
+ default:
+ for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) {
+ if (gate == NI_M_RTSI_GATE_SEL(i))
+ return NI_GPCT_RTSI_GATE_SELECT(i);
+ }
+ for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) {
+ if (gate == NI_M_PFI_GATE_SEL(i))
+ return NI_GPCT_PFI_GATE_SELECT(i);
+ }
+ BUG();
+ break;
+ }
+ return 0;
+};
+
+static unsigned ni_660x_gate2_to_generic_gate(unsigned gate)
+{
+ unsigned i;
+
+ switch (gate) {
+ case NI_660X_SRC_PIN_I_GATE2_SEL:
+ return NI_GPCT_SOURCE_PIN_i_GATE_SELECT;
+ case NI_660X_UD_PIN_I_GATE2_SEL:
+ return NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT;
+ case NI_660X_NEXT_SRC_GATE2_SEL:
+ return NI_GPCT_NEXT_SOURCE_GATE_SELECT;
+ case NI_660X_NEXT_OUT_GATE2_SEL:
+ return NI_GPCT_NEXT_OUT_GATE_SELECT;
+ case NI_660X_SELECTED_GATE2_SEL:
+ return NI_GPCT_SELECTED_GATE_GATE_SELECT;
+ case NI_660X_LOGIC_LOW_GATE2_SEL:
+ return NI_GPCT_LOGIC_LOW_GATE_SELECT;
+ default:
+ for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) {
+ if (gate == NI_660X_RTSI_GATE2_SEL(i))
+ return NI_GPCT_RTSI_GATE_SELECT(i);
+ }
+ for (i = 0; i <= NI_660X_MAX_UP_DOWN_PIN; ++i) {
+ if (gate == NI_660X_UD_PIN_GATE2_SEL(i))
+ return NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i);
+ }
+ BUG();
+ break;
+ }
+ return 0;
+};
+
+static unsigned ni_m_gate2_to_generic_gate(unsigned gate)
+{
+ /*
+ * FIXME: the second gate sources for the m series are undocumented,
+ * so we just return the raw bits for now.
+ */
+ switch (gate) {
+ default:
+ return gate;
+ }
+ return 0;
+};
+
+static int ni_tio_get_gate_src(struct ni_gpct *counter, unsigned gate_index,
+ unsigned int *gate_source)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned mode = ni_tio_get_soft_copy(counter, NITIO_MODE_REG(cidx));
+ unsigned gate2_reg = NITIO_GATE2_REG(cidx);
+ unsigned gate;
+
+ switch (gate_index) {
+ case 0:
+ if ((mode & GI_GATING_MODE_MASK) == GI_GATING_DISABLED) {
+ *gate_source = NI_GPCT_DISABLED_GATE_SELECT;
+ return 0;
+ }
+
+ gate = GI_BITS_TO_GATE(ni_tio_get_soft_copy(counter,
+ NITIO_INPUT_SEL_REG(cidx)));
+
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_e_series:
+ case ni_gpct_variant_m_series:
+ default:
+ *gate_source = ni_m_gate_to_generic_gate(gate);
+ break;
+ case ni_gpct_variant_660x:
+ *gate_source = ni_660x_gate_to_generic_gate(gate);
+ break;
+ }
+ if (mode & GI_GATE_POL_INVERT)
+ *gate_source |= CR_INVERT;
+ if ((mode & GI_GATING_MODE_MASK) != GI_LEVEL_GATING)
+ *gate_source |= CR_EDGE;
+ break;
+ case 1:
+ if ((mode & GI_GATING_MODE_MASK) == GI_GATING_DISABLED ||
+ !(counter_dev->regs[gate2_reg] & GI_GATE2_MODE)) {
+ *gate_source = NI_GPCT_DISABLED_GATE_SELECT;
+ return 0;
+ }
+
+ gate = GI_BITS_TO_GATE2(counter_dev->regs[gate2_reg]);
+
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_e_series:
+ case ni_gpct_variant_m_series:
+ default:
+ *gate_source = ni_m_gate2_to_generic_gate(gate);
+ break;
+ case ni_gpct_variant_660x:
+ *gate_source = ni_660x_gate2_to_generic_gate(gate);
+ break;
+ }
+ if (counter_dev->regs[gate2_reg] & GI_GATE2_POL_INVERT)
+ *gate_source |= CR_INVERT;
+ /* second gate can't have edge/level mode set independently */
+ if ((mode & GI_GATING_MODE_MASK) != GI_LEVEL_GATING)
+ *gate_source |= CR_EDGE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int ni_tio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_gpct *counter = s->private;
+ unsigned cidx = counter->counter_index;
+ unsigned status;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SET_COUNTER_MODE:
+ return ni_tio_set_counter_mode(counter, data[1]);
+ case INSN_CONFIG_ARM:
+ return ni_tio_arm(counter, 1, data[1]);
+ case INSN_CONFIG_DISARM:
+ ni_tio_arm(counter, 0, 0);
+ return 0;
+ case INSN_CONFIG_GET_COUNTER_STATUS:
+ data[1] = 0;
+ status = read_register(counter, NITIO_SHARED_STATUS_REG(cidx));
+ if (status & GI_ARMED(cidx)) {
+ data[1] |= COMEDI_COUNTER_ARMED;
+ if (status & GI_COUNTING(cidx))
+ data[1] |= COMEDI_COUNTER_COUNTING;
+ }
+ data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING;
+ return 0;
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ return ni_tio_set_clock_src(counter, data[1], data[2]);
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ ni_tio_get_clock_src(counter, &data[1], &data[2]);
+ return 0;
+ case INSN_CONFIG_SET_GATE_SRC:
+ return ni_tio_set_gate_src(counter, data[1], data[2]);
+ case INSN_CONFIG_GET_GATE_SRC:
+ return ni_tio_get_gate_src(counter, data[1], &data[2]);
+ case INSN_CONFIG_SET_OTHER_SRC:
+ return ni_tio_set_other_src(counter, data[1], data[2]);
+ case INSN_CONFIG_RESET:
+ ni_tio_reset_count_and_disarm(counter);
+ return 0;
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(ni_tio_insn_config);
+
+static unsigned int ni_tio_read_sw_save_reg(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ unsigned cidx = counter->counter_index;
+ unsigned int val;
+
+ ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), GI_SAVE_TRACE, 0);
+ ni_tio_set_bits(counter, NITIO_CMD_REG(cidx),
+ GI_SAVE_TRACE, GI_SAVE_TRACE);
+
+ /*
+ * The count doesn't get latched until the next clock edge, so it is
+ * possible the count may change (once) while we are reading. Since
+ * the read of the SW_Save_Reg isn't atomic (apparently even when it's
+ * a 32 bit register according to 660x docs), we need to read twice
+ * and make sure the reading hasn't changed. If it has, a third read
+ * will be correct since the count value will definitely have latched
+ * by then.
+ */
+ val = read_register(counter, NITIO_SW_SAVE_REG(cidx));
+ if (val != read_register(counter, NITIO_SW_SAVE_REG(cidx)))
+ val = read_register(counter, NITIO_SW_SAVE_REG(cidx));
+
+ return val;
+}
+
+int ni_tio_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_gpct *counter = s->private;
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+ unsigned cidx = counter->counter_index;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ switch (channel) {
+ case 0:
+ data[i] = ni_tio_read_sw_save_reg(dev, s);
+ break;
+ case 1:
+ data[i] = counter_dev->regs[NITIO_LOADA_REG(cidx)];
+ break;
+ case 2:
+ data[i] = counter_dev->regs[NITIO_LOADB_REG(cidx)];
+ break;
+ }
+ }
+ return insn->n;
+}
+EXPORT_SYMBOL_GPL(ni_tio_insn_read);
+
+static unsigned ni_tio_next_load_register(struct ni_gpct *counter)
+{
+ unsigned cidx = counter->counter_index;
+ const unsigned bits =
+ read_register(counter, NITIO_SHARED_STATUS_REG(cidx));
+
+ return (bits & GI_NEXT_LOAD_SRC(cidx))
+ ? NITIO_LOADB_REG(cidx)
+ : NITIO_LOADA_REG(cidx);
+}
+
+int ni_tio_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_gpct *counter = s->private;
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ const unsigned channel = CR_CHAN(insn->chanspec);
+ unsigned cidx = counter->counter_index;
+ unsigned load_reg;
+
+ if (insn->n < 1)
+ return 0;
+ switch (channel) {
+ case 0:
+ /*
+ * Unsafe if counter is armed.
+ * Should probably check status and return -EBUSY if armed.
+ */
+
+ /*
+ * Don't disturb load source select, just use whichever
+ * load register is already selected.
+ */
+ load_reg = ni_tio_next_load_register(counter);
+ write_register(counter, data[0], load_reg);
+ ni_tio_set_bits_transient(counter, NITIO_CMD_REG(cidx),
+ 0, 0, GI_LOAD);
+ /* restore load reg */
+ write_register(counter, counter_dev->regs[load_reg], load_reg);
+ break;
+ case 1:
+ counter_dev->regs[NITIO_LOADA_REG(cidx)] = data[0];
+ write_register(counter, data[0], NITIO_LOADA_REG(cidx));
+ break;
+ case 2:
+ counter_dev->regs[NITIO_LOADB_REG(cidx)] = data[0];
+ write_register(counter, data[0], NITIO_LOADB_REG(cidx));
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ni_tio_insn_write);
+
+void ni_tio_init_counter(struct ni_gpct *counter)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+
+ ni_tio_reset_count_and_disarm(counter);
+
+ /* initialize counter registers */
+ counter_dev->regs[NITIO_AUTO_INC_REG(cidx)] = 0x0;
+ write_register(counter, 0x0, NITIO_AUTO_INC_REG(cidx));
+
+ ni_tio_set_bits(counter, NITIO_CMD_REG(cidx),
+ ~0, GI_SYNC_GATE);
+
+ ni_tio_set_bits(counter, NITIO_MODE_REG(cidx), ~0, 0);
+
+ counter_dev->regs[NITIO_LOADA_REG(cidx)] = 0x0;
+ write_register(counter, 0x0, NITIO_LOADA_REG(cidx));
+
+ counter_dev->regs[NITIO_LOADB_REG(cidx)] = 0x0;
+ write_register(counter, 0x0, NITIO_LOADB_REG(cidx));
+
+ ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), ~0, 0);
+
+ if (ni_tio_counting_mode_registers_present(counter_dev))
+ ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), ~0, 0);
+
+ if (ni_tio_has_gate2_registers(counter_dev)) {
+ counter_dev->regs[NITIO_GATE2_REG(cidx)] = 0x0;
+ write_register(counter, 0x0, NITIO_GATE2_REG(cidx));
+ }
+
+ ni_tio_set_bits(counter, NITIO_DMA_CFG_REG(cidx), ~0, 0x0);
+
+ ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx), ~0, 0x0);
+}
+EXPORT_SYMBOL_GPL(ni_tio_init_counter);
+
+struct ni_gpct_device *
+ni_gpct_device_construct(struct comedi_device *dev,
+ void (*write_register)(struct ni_gpct *counter,
+ unsigned bits,
+ enum ni_gpct_register reg),
+ unsigned (*read_register)(struct ni_gpct *counter,
+ enum ni_gpct_register reg),
+ enum ni_gpct_variant variant,
+ unsigned num_counters)
+{
+ struct ni_gpct_device *counter_dev;
+ struct ni_gpct *counter;
+ unsigned i;
+
+ if (num_counters == 0)
+ return NULL;
+
+ counter_dev = kzalloc(sizeof(*counter_dev), GFP_KERNEL);
+ if (!counter_dev)
+ return NULL;
+
+ counter_dev->dev = dev;
+ counter_dev->write_register = write_register;
+ counter_dev->read_register = read_register;
+ counter_dev->variant = variant;
+
+ spin_lock_init(&counter_dev->regs_lock);
+
+ counter_dev->counters = kcalloc(num_counters, sizeof(*counter),
+ GFP_KERNEL);
+ if (!counter_dev->counters) {
+ kfree(counter_dev);
+ return NULL;
+ }
+
+ for (i = 0; i < num_counters; ++i) {
+ counter = &counter_dev->counters[i];
+ counter->counter_dev = counter_dev;
+ spin_lock_init(&counter->lock);
+ }
+ counter_dev->num_counters = num_counters;
+
+ return counter_dev;
+}
+EXPORT_SYMBOL_GPL(ni_gpct_device_construct);
+
+void ni_gpct_device_destroy(struct ni_gpct_device *counter_dev)
+{
+ if (!counter_dev->counters)
+ return;
+ kfree(counter_dev->counters);
+ kfree(counter_dev);
+}
+EXPORT_SYMBOL_GPL(ni_gpct_device_destroy);
+
+static int __init ni_tio_init_module(void)
+{
+ return 0;
+}
+module_init(ni_tio_init_module);
+
+static void __exit ni_tio_cleanup_module(void)
+{
+}
+module_exit(ni_tio_cleanup_module);
+
+MODULE_AUTHOR("Comedi <comedi@comedi.org>");
+MODULE_DESCRIPTION("Comedi support for NI general-purpose counters");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_tio.h b/drivers/staging/comedi/drivers/ni_tio.h
new file mode 100644
index 000000000..25aedd0e5
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tio.h
@@ -0,0 +1,154 @@
+/*
+ drivers/ni_tio.h
+ Header file for NI general purpose counter support code (ni_tio.c)
+
+ COMEDI - Linux Control and Measurement Device Interface
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _COMEDI_NI_TIO_H
+#define _COMEDI_NI_TIO_H
+
+#include "../comedidev.h"
+
+/* forward declarations */
+struct mite_struct;
+struct ni_gpct_device;
+
+enum ni_gpct_register {
+ NITIO_G0_AUTO_INC,
+ NITIO_G1_AUTO_INC,
+ NITIO_G2_AUTO_INC,
+ NITIO_G3_AUTO_INC,
+ NITIO_G0_CMD,
+ NITIO_G1_CMD,
+ NITIO_G2_CMD,
+ NITIO_G3_CMD,
+ NITIO_G0_HW_SAVE,
+ NITIO_G1_HW_SAVE,
+ NITIO_G2_HW_SAVE,
+ NITIO_G3_HW_SAVE,
+ NITIO_G0_SW_SAVE,
+ NITIO_G1_SW_SAVE,
+ NITIO_G2_SW_SAVE,
+ NITIO_G3_SW_SAVE,
+ NITIO_G0_MODE,
+ NITIO_G1_MODE,
+ NITIO_G2_MODE,
+ NITIO_G3_MODE,
+ NITIO_G0_LOADA,
+ NITIO_G1_LOADA,
+ NITIO_G2_LOADA,
+ NITIO_G3_LOADA,
+ NITIO_G0_LOADB,
+ NITIO_G1_LOADB,
+ NITIO_G2_LOADB,
+ NITIO_G3_LOADB,
+ NITIO_G0_INPUT_SEL,
+ NITIO_G1_INPUT_SEL,
+ NITIO_G2_INPUT_SEL,
+ NITIO_G3_INPUT_SEL,
+ NITIO_G0_CNT_MODE,
+ NITIO_G1_CNT_MODE,
+ NITIO_G2_CNT_MODE,
+ NITIO_G3_CNT_MODE,
+ NITIO_G0_GATE2,
+ NITIO_G1_GATE2,
+ NITIO_G2_GATE2,
+ NITIO_G3_GATE2,
+ NITIO_G01_STATUS,
+ NITIO_G23_STATUS,
+ NITIO_G01_RESET,
+ NITIO_G23_RESET,
+ NITIO_G01_STATUS1,
+ NITIO_G23_STATUS1,
+ NITIO_G01_STATUS2,
+ NITIO_G23_STATUS2,
+ NITIO_G0_DMA_CFG,
+ NITIO_G1_DMA_CFG,
+ NITIO_G2_DMA_CFG,
+ NITIO_G3_DMA_CFG,
+ NITIO_G0_DMA_STATUS,
+ NITIO_G1_DMA_STATUS,
+ NITIO_G2_DMA_STATUS,
+ NITIO_G3_DMA_STATUS,
+ NITIO_G0_ABZ,
+ NITIO_G1_ABZ,
+ NITIO_G0_INT_ACK,
+ NITIO_G1_INT_ACK,
+ NITIO_G2_INT_ACK,
+ NITIO_G3_INT_ACK,
+ NITIO_G0_STATUS,
+ NITIO_G1_STATUS,
+ NITIO_G2_STATUS,
+ NITIO_G3_STATUS,
+ NITIO_G0_INT_ENA,
+ NITIO_G1_INT_ENA,
+ NITIO_G2_INT_ENA,
+ NITIO_G3_INT_ENA,
+ NITIO_NUM_REGS,
+};
+
+enum ni_gpct_variant {
+ ni_gpct_variant_e_series,
+ ni_gpct_variant_m_series,
+ ni_gpct_variant_660x
+};
+
+struct ni_gpct {
+ struct ni_gpct_device *counter_dev;
+ unsigned counter_index;
+ unsigned chip_index;
+ uint64_t clock_period_ps; /* clock period in picoseconds */
+ struct mite_channel *mite_chan;
+ spinlock_t lock;
+};
+
+struct ni_gpct_device {
+ struct comedi_device *dev;
+ void (*write_register)(struct ni_gpct *counter, unsigned bits,
+ enum ni_gpct_register reg);
+ unsigned (*read_register)(struct ni_gpct *counter,
+ enum ni_gpct_register reg);
+ enum ni_gpct_variant variant;
+ struct ni_gpct *counters;
+ unsigned num_counters;
+ unsigned regs[NITIO_NUM_REGS];
+ spinlock_t regs_lock;
+};
+
+struct ni_gpct_device *
+ni_gpct_device_construct(struct comedi_device *,
+ void (*write_register)(struct ni_gpct *,
+ unsigned bits,
+ enum ni_gpct_register),
+ unsigned (*read_register)(struct ni_gpct *,
+ enum ni_gpct_register),
+ enum ni_gpct_variant,
+ unsigned num_counters);
+void ni_gpct_device_destroy(struct ni_gpct_device *);
+void ni_tio_init_counter(struct ni_gpct *);
+int ni_tio_insn_read(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data);
+int ni_tio_insn_config(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data);
+int ni_tio_insn_write(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data);
+int ni_tio_cmd(struct comedi_device *, struct comedi_subdevice *);
+int ni_tio_cmdtest(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_cmd *);
+int ni_tio_cancel(struct ni_gpct *);
+void ni_tio_handle_interrupt(struct ni_gpct *, struct comedi_subdevice *);
+void ni_tio_set_mite_channel(struct ni_gpct *, struct mite_channel *);
+void ni_tio_acknowledge(struct ni_gpct *);
+
+#endif /* _COMEDI_NI_TIO_H */
diff --git a/drivers/staging/comedi/drivers/ni_tio_internal.h b/drivers/staging/comedi/drivers/ni_tio_internal.h
new file mode 100644
index 000000000..2bceae493
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tio_internal.h
@@ -0,0 +1,245 @@
+/*
+ drivers/ni_tio_internal.h
+ Header file for NI general purpose counter support code (ni_tio.c and
+ ni_tiocmd.c)
+
+ COMEDI - Linux Control and Measurement Device Interface
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _COMEDI_NI_TIO_INTERNAL_H
+#define _COMEDI_NI_TIO_INTERNAL_H
+
+#include "ni_tio.h"
+
+#define NITIO_AUTO_INC_REG(x) (NITIO_G0_AUTO_INC + (x))
+#define GI_AUTO_INC_MASK 0xff
+#define NITIO_CMD_REG(x) (NITIO_G0_CMD + (x))
+#define GI_ARM (1 << 0)
+#define GI_SAVE_TRACE (1 << 1)
+#define GI_LOAD (1 << 2)
+#define GI_DISARM (1 << 4)
+#define GI_CNT_DIR(x) (((x) & 0x3) << 5)
+#define GI_CNT_DIR_MASK (3 << 5)
+#define GI_WRITE_SWITCH (1 << 7)
+#define GI_SYNC_GATE (1 << 8)
+#define GI_LITTLE_BIG_ENDIAN (1 << 9)
+#define GI_BANK_SWITCH_START (1 << 10)
+#define GI_BANK_SWITCH_MODE (1 << 11)
+#define GI_BANK_SWITCH_ENABLE (1 << 12)
+#define GI_ARM_COPY (1 << 13)
+#define GI_SAVE_TRACE_COPY (1 << 14)
+#define GI_DISARM_COPY (1 << 15)
+#define NITIO_HW_SAVE_REG(x) (NITIO_G0_HW_SAVE + (x))
+#define NITIO_SW_SAVE_REG(x) (NITIO_G0_SW_SAVE + (x))
+#define NITIO_MODE_REG(x) (NITIO_G0_MODE + (x))
+#define GI_GATING_DISABLED (0 << 0)
+#define GI_LEVEL_GATING (1 << 0)
+#define GI_RISING_EDGE_GATING (2 << 0)
+#define GI_FALLING_EDGE_GATING (3 << 0)
+#define GI_GATING_MODE_MASK (3 << 0)
+#define GI_GATE_ON_BOTH_EDGES (1 << 2)
+#define GI_EDGE_GATE_STARTS_STOPS (0 << 3)
+#define GI_EDGE_GATE_STOPS_STARTS (1 << 3)
+#define GI_EDGE_GATE_STARTS (2 << 3)
+#define GI_EDGE_GATE_NO_STARTS_OR_STOPS (3 << 3)
+#define GI_EDGE_GATE_MODE_MASK (3 << 3)
+#define GI_STOP_ON_GATE (0 << 5)
+#define GI_STOP_ON_GATE_OR_TC (1 << 5)
+#define GI_STOP_ON_GATE_OR_SECOND_TC (2 << 5)
+#define GI_STOP_MODE_MASK (3 << 5)
+#define GI_LOAD_SRC_SEL (1 << 7)
+#define GI_OUTPUT_TC_PULSE (1 << 8)
+#define GI_OUTPUT_TC_TOGGLE (2 << 8)
+#define GI_OUTPUT_TC_OR_GATE_TOGGLE (3 << 8)
+#define GI_OUTPUT_MODE_MASK (3 << 8)
+#define GI_NO_HARDWARE_DISARM (0 << 10)
+#define GI_DISARM_AT_TC (1 << 10)
+#define GI_DISARM_AT_GATE (2 << 10)
+#define GI_DISARM_AT_TC_OR_GATE (3 << 10)
+#define GI_COUNTING_ONCE_MASK (3 << 10)
+#define GI_LOADING_ON_TC (1 << 12)
+#define GI_GATE_POL_INVERT (1 << 13)
+#define GI_LOADING_ON_GATE (1 << 14)
+#define GI_RELOAD_SRC_SWITCHING (1 << 15)
+#define NITIO_LOADA_REG(x) (NITIO_G0_LOADA + (x))
+#define NITIO_LOADB_REG(x) (NITIO_G0_LOADB + (x))
+#define NITIO_INPUT_SEL_REG(x) (NITIO_G0_INPUT_SEL + (x))
+#define GI_READ_ACKS_IRQ (1 << 0)
+#define GI_WRITE_ACKS_IRQ (1 << 1)
+#define GI_BITS_TO_SRC(x) (((x) >> 2) & 0x1f)
+#define GI_SRC_SEL(x) (((x) & 0x1f) << 2)
+#define GI_SRC_SEL_MASK (0x1f << 2)
+#define GI_BITS_TO_GATE(x) (((x) >> 7) & 0x1f)
+#define GI_GATE_SEL(x) (((x) & 0x1f) << 7)
+#define GI_GATE_SEL_MASK (0x1f << 7)
+#define GI_GATE_SEL_LOAD_SRC (1 << 12)
+#define GI_OR_GATE (1 << 13)
+#define GI_OUTPUT_POL_INVERT (1 << 14)
+#define GI_SRC_POL_INVERT (1 << 15)
+#define NITIO_CNT_MODE_REG(x) (NITIO_G0_CNT_MODE + (x))
+#define GI_CNT_MODE(x) (((x) & 0x7) << 0)
+#define GI_CNT_MODE_NORMAL GI_CNT_MODE(0)
+#define GI_CNT_MODE_QUADX1 GI_CNT_MODE(1)
+#define GI_CNT_MODE_QUADX2 GI_CNT_MODE(2)
+#define GI_CNT_MODE_QUADX4 GI_CNT_MODE(3)
+#define GI_CNT_MODE_TWO_PULSE GI_CNT_MODE(4)
+#define GI_CNT_MODE_SYNC_SRC GI_CNT_MODE(6)
+#define GI_CNT_MODE_MASK (7 << 0)
+#define GI_INDEX_MODE (1 << 4)
+#define GI_INDEX_PHASE(x) (((x) & 0x3) << 5)
+#define GI_INDEX_PHASE_MASK (3 << 5)
+#define GI_HW_ARM_ENA (1 << 7)
+#define GI_HW_ARM_SEL(x) ((x) << 8)
+#define GI_660X_HW_ARM_SEL_MASK (0x7 << 8)
+#define GI_M_HW_ARM_SEL_MASK (0x1f << 8)
+#define GI_660X_PRESCALE_X8 (1 << 12)
+#define GI_M_PRESCALE_X8 (1 << 13)
+#define GI_660X_ALT_SYNC (1 << 13)
+#define GI_M_ALT_SYNC (1 << 14)
+#define GI_660X_PRESCALE_X2 (1 << 14)
+#define GI_M_PRESCALE_X2 (1 << 15)
+#define NITIO_GATE2_REG(x) (NITIO_G0_GATE2 + (x))
+#define GI_GATE2_MODE (1 << 0)
+#define GI_BITS_TO_GATE2(x) (((x) >> 7) & 0x1f)
+#define GI_GATE2_SEL(x) (((x) & 0x1f) << 7)
+#define GI_GATE2_SEL_MASK (0x1f << 7)
+#define GI_GATE2_POL_INVERT (1 << 13)
+#define GI_GATE2_SUBSEL (1 << 14)
+#define GI_SRC_SUBSEL (1 << 15)
+#define NITIO_SHARED_STATUS_REG(x) (NITIO_G01_STATUS + ((x) / 2))
+#define GI_SAVE(x) (((x) % 2) ? (1 << 1) : (1 << 0))
+#define GI_COUNTING(x) (((x) % 2) ? (1 << 3) : (1 << 2))
+#define GI_NEXT_LOAD_SRC(x) (((x) % 2) ? (1 << 5) : (1 << 4))
+#define GI_STALE_DATA(x) (((x) % 2) ? (1 << 7) : (1 << 6))
+#define GI_ARMED(x) (((x) % 2) ? (1 << 9) : (1 << 8))
+#define GI_NO_LOAD_BETWEEN_GATES(x) (((x) % 2) ? (1 << 11) : (1 << 10))
+#define GI_TC_ERROR(x) (((x) % 2) ? (1 << 13) : (1 << 12))
+#define GI_GATE_ERROR(x) (((x) % 2) ? (1 << 15) : (1 << 14))
+#define NITIO_RESET_REG(x) (NITIO_G01_RESET + ((x) / 2))
+#define GI_RESET(x) (1 << (2 + ((x) % 2)))
+#define NITIO_STATUS1_REG(x) (NITIO_G01_STATUS1 + ((x) / 2))
+#define NITIO_STATUS2_REG(x) (NITIO_G01_STATUS2 + ((x) / 2))
+#define GI_OUTPUT(x) (((x) % 2) ? (1 << 1) : (1 << 0))
+#define GI_HW_SAVE(x) (((x) % 2) ? (1 << 13) : (1 << 12))
+#define GI_PERMANENT_STALE(x) (((x) % 2) ? (1 << 15) : (1 << 14))
+#define NITIO_DMA_CFG_REG(x) (NITIO_G0_DMA_CFG + (x))
+#define GI_DMA_ENABLE (1 << 0)
+#define GI_DMA_WRITE (1 << 1)
+#define GI_DMA_INT_ENA (1 << 2)
+#define GI_DMA_RESET (1 << 3)
+#define GI_DMA_BANKSW_ERROR (1 << 4)
+#define NITIO_DMA_STATUS_REG(x) (NITIO_G0_DMA_STATUS + (x))
+#define GI_DMA_READBANK (1 << 13)
+#define GI_DRQ_ERROR (1 << 14)
+#define GI_DRQ_STATUS (1 << 15)
+#define NITIO_ABZ_REG(x) (NITIO_G0_ABZ + (x))
+#define NITIO_INT_ACK_REG(x) (NITIO_G0_INT_ACK + (x))
+#define GI_GATE_ERROR_CONFIRM(x) (((x) % 2) ? (1 << 1) : (1 << 5))
+#define GI_TC_ERROR_CONFIRM(x) (((x) % 2) ? (1 << 2) : (1 << 6))
+#define GI_TC_INTERRUPT_ACK (1 << 14)
+#define GI_GATE_INTERRUPT_ACK (1 << 15)
+#define NITIO_STATUS_REG(x) (NITIO_G0_STATUS + (x))
+#define GI_GATE_INTERRUPT (1 << 2)
+#define GI_TC (1 << 3)
+#define GI_INTERRUPT (1 << 15)
+#define NITIO_INT_ENA_REG(x) (NITIO_G0_INT_ENA + (x))
+#define GI_TC_INTERRUPT_ENABLE(x) (((x) % 2) ? (1 << 9) : (1 << 6))
+#define GI_GATE_INTERRUPT_ENABLE(x) (((x) % 2) ? (1 << 10) : (1 << 8))
+
+static inline void write_register(struct ni_gpct *counter, unsigned bits,
+ enum ni_gpct_register reg)
+{
+ BUG_ON(reg >= NITIO_NUM_REGS);
+ counter->counter_dev->write_register(counter, bits, reg);
+}
+
+static inline unsigned read_register(struct ni_gpct *counter,
+ enum ni_gpct_register reg)
+{
+ BUG_ON(reg >= NITIO_NUM_REGS);
+ return counter->counter_dev->read_register(counter, reg);
+}
+
+static inline int ni_tio_counting_mode_registers_present(const struct
+ ni_gpct_device
+ *counter_dev)
+{
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_e_series:
+ return 0;
+ case ni_gpct_variant_m_series:
+ case ni_gpct_variant_660x:
+ return 1;
+ default:
+ BUG();
+ break;
+ }
+ return 0;
+}
+
+static inline void ni_tio_set_bits_transient(struct ni_gpct *counter,
+ enum ni_gpct_register
+ register_index, unsigned bit_mask,
+ unsigned bit_values,
+ unsigned transient_bit_values)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned long flags;
+
+ BUG_ON(register_index >= NITIO_NUM_REGS);
+ spin_lock_irqsave(&counter_dev->regs_lock, flags);
+ counter_dev->regs[register_index] &= ~bit_mask;
+ counter_dev->regs[register_index] |= (bit_values & bit_mask);
+ write_register(counter,
+ counter_dev->regs[register_index] | transient_bit_values,
+ register_index);
+ mmiowb();
+ spin_unlock_irqrestore(&counter_dev->regs_lock, flags);
+}
+
+/* ni_tio_set_bits( ) is for safely writing to registers whose bits may be
+ * twiddled in interrupt context, or whose software copy may be read in
+ * interrupt context.
+ */
+static inline void ni_tio_set_bits(struct ni_gpct *counter,
+ enum ni_gpct_register register_index,
+ unsigned bit_mask, unsigned bit_values)
+{
+ ni_tio_set_bits_transient(counter, register_index, bit_mask, bit_values,
+ 0x0);
+}
+
+/* ni_tio_get_soft_copy( ) is for safely reading the software copy of a register
+whose bits might be modified in interrupt context, or whose software copy
+might need to be read in interrupt context.
+*/
+static inline unsigned ni_tio_get_soft_copy(const struct ni_gpct *counter,
+ enum ni_gpct_register
+ register_index)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned long flags;
+ unsigned value;
+
+ BUG_ON(register_index >= NITIO_NUM_REGS);
+ spin_lock_irqsave(&counter_dev->regs_lock, flags);
+ value = counter_dev->regs[register_index];
+ spin_unlock_irqrestore(&counter_dev->regs_lock, flags);
+ return value;
+}
+
+int ni_tio_arm(struct ni_gpct *counter, int arm, unsigned start_trigger);
+int ni_tio_set_gate_src(struct ni_gpct *counter, unsigned gate_index,
+ unsigned int gate_source);
+
+#endif /* _COMEDI_NI_TIO_INTERNAL_H */
diff --git a/drivers/staging/comedi/drivers/ni_tiocmd.c b/drivers/staging/comedi/drivers/ni_tiocmd.c
new file mode 100644
index 000000000..9b124b09e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tiocmd.c
@@ -0,0 +1,484 @@
+/*
+ comedi/drivers/ni_tiocmd.c
+ Command support for NI general purpose counters
+
+ Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+ * Module: ni_tiocmd
+ * Description: National Instruments general purpose counters command support
+ * Author: J.P. Mellor <jpmellor@rose-hulman.edu>,
+ * Herman.Bruyninckx@mech.kuleuven.ac.be,
+ * Wim.Meeussen@mech.kuleuven.ac.be,
+ * Klaas.Gadeyne@mech.kuleuven.ac.be,
+ * Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Updated: Fri, 11 Apr 2008 12:32:35 +0100
+ * Status: works
+ *
+ * This module is not used directly by end-users. Rather, it
+ * is used by other drivers (for example ni_660x and ni_pcimio)
+ * to provide command support for NI's general purpose counters.
+ * It was originally split out of ni_tio.c to stop the 'ni_tio'
+ * module depending on the 'mite' module.
+ *
+ * References:
+ * DAQ 660x Register-Level Programmer Manual (NI 370505A-01)
+ * DAQ 6601/6602 User Manual (NI 322137B-01)
+ * 340934b.pdf DAQ-STC reference manual
+ */
+
+/*
+TODO:
+ Support use of both banks X and Y
+*/
+
+#include <linux/module.h>
+#include "ni_tio_internal.h"
+#include "mite.h"
+
+static void ni_tio_configure_dma(struct ni_gpct *counter,
+ bool enable, bool read)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ unsigned mask;
+ unsigned bits;
+
+ mask = GI_READ_ACKS_IRQ | GI_WRITE_ACKS_IRQ;
+ bits = 0;
+
+ if (enable) {
+ if (read)
+ bits |= GI_READ_ACKS_IRQ;
+ else
+ bits |= GI_WRITE_ACKS_IRQ;
+ }
+ ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), mask, bits);
+
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_e_series:
+ break;
+ case ni_gpct_variant_m_series:
+ case ni_gpct_variant_660x:
+ mask = GI_DMA_ENABLE | GI_DMA_INT_ENA | GI_DMA_WRITE;
+ bits = 0;
+
+ if (enable)
+ bits |= GI_DMA_ENABLE | GI_DMA_INT_ENA;
+ if (!read)
+ bits |= GI_DMA_WRITE;
+ ni_tio_set_bits(counter, NITIO_DMA_CFG_REG(cidx), mask, bits);
+ break;
+ }
+}
+
+static int ni_tio_input_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct ni_gpct *counter = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+ int ret = 0;
+
+ if (trig_num != cmd->start_src)
+ return -EINVAL;
+
+ spin_lock_irqsave(&counter->lock, flags);
+ if (counter->mite_chan)
+ mite_dma_arm(counter->mite_chan);
+ else
+ ret = -EIO;
+ spin_unlock_irqrestore(&counter->lock, flags);
+ if (ret < 0)
+ return ret;
+ ret = ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE);
+ s->async->inttrig = NULL;
+
+ return ret;
+}
+
+static int ni_tio_input_cmd(struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int ret = 0;
+
+ /* write alloc the entire buffer */
+ comedi_buf_write_alloc(s, async->prealloc_bufsz);
+ counter->mite_chan->dir = COMEDI_INPUT;
+ switch (counter_dev->variant) {
+ case ni_gpct_variant_m_series:
+ case ni_gpct_variant_660x:
+ mite_prep_dma(counter->mite_chan, 32, 32);
+ break;
+ case ni_gpct_variant_e_series:
+ mite_prep_dma(counter->mite_chan, 16, 32);
+ break;
+ default:
+ BUG();
+ break;
+ }
+ ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), GI_SAVE_TRACE, 0);
+ ni_tio_configure_dma(counter, true, true);
+
+ if (cmd->start_src == TRIG_INT) {
+ async->inttrig = &ni_tio_input_inttrig;
+ } else { /* TRIG_NOW || TRIG_EXT || TRIG_OTHER */
+ async->inttrig = NULL;
+ mite_dma_arm(counter->mite_chan);
+
+ if (cmd->start_src == TRIG_NOW)
+ ret = ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE);
+ else if (cmd->start_src == TRIG_EXT)
+ ret = ni_tio_arm(counter, 1, cmd->start_arg);
+ }
+ return ret;
+}
+
+static int ni_tio_output_cmd(struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+
+ dev_err(counter->counter_dev->dev->class_dev,
+ "output commands not yet implemented.\n");
+ return -ENOTSUPP;
+
+ counter->mite_chan->dir = COMEDI_OUTPUT;
+ mite_prep_dma(counter->mite_chan, 32, 32);
+ ni_tio_configure_dma(counter, true, false);
+ mite_dma_arm(counter->mite_chan);
+ return ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE);
+}
+
+static int ni_tio_cmd_setup(struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct ni_gpct *counter = s->private;
+ unsigned cidx = counter->counter_index;
+ int set_gate_source = 0;
+ unsigned gate_source;
+ int retval = 0;
+
+ if (cmd->scan_begin_src == TRIG_EXT) {
+ set_gate_source = 1;
+ gate_source = cmd->scan_begin_arg;
+ } else if (cmd->convert_src == TRIG_EXT) {
+ set_gate_source = 1;
+ gate_source = cmd->convert_arg;
+ }
+ if (set_gate_source)
+ retval = ni_tio_set_gate_src(counter, 0, gate_source);
+ if (cmd->flags & CMDF_WAKE_EOS) {
+ ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx),
+ GI_GATE_INTERRUPT_ENABLE(cidx),
+ GI_GATE_INTERRUPT_ENABLE(cidx));
+ }
+ return retval;
+}
+
+int ni_tio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int retval = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&counter->lock, flags);
+ if (!counter->mite_chan) {
+ dev_err(counter->counter_dev->dev->class_dev,
+ "commands only supported with DMA. ");
+ dev_err(counter->counter_dev->dev->class_dev,
+ "Interrupt-driven commands not yet implemented.\n");
+ retval = -EIO;
+ } else {
+ retval = ni_tio_cmd_setup(s);
+ if (retval == 0) {
+ if (cmd->flags & CMDF_WRITE)
+ retval = ni_tio_output_cmd(s);
+ else
+ retval = ni_tio_input_cmd(s);
+ }
+ }
+ spin_unlock_irqrestore(&counter->lock, flags);
+ return retval;
+}
+EXPORT_SYMBOL_GPL(ni_tio_cmd);
+
+int ni_tio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct ni_gpct *counter = s->private;
+ int err = 0;
+ unsigned int sources;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ sources = TRIG_NOW | TRIG_INT | TRIG_OTHER;
+ if (ni_tio_counting_mode_registers_present(counter->counter_dev))
+ sources |= TRIG_EXT;
+ err |= comedi_check_trigger_src(&cmd->start_src, sources);
+
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_EXT | TRIG_OTHER);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_NOW | TRIG_EXT | TRIG_OTHER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->convert_src != TRIG_NOW && cmd->scan_begin_src != TRIG_FOLLOW)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_INT:
+ case TRIG_OTHER:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /* start_arg is the start_trigger passed to ni_tio_arm() */
+ break;
+ }
+
+ if (cmd->scan_begin_src != TRIG_EXT)
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src != TRIG_EXT)
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ni_tio_cmdtest);
+
+int ni_tio_cancel(struct ni_gpct *counter)
+{
+ unsigned cidx = counter->counter_index;
+ unsigned long flags;
+
+ ni_tio_arm(counter, 0, 0);
+ spin_lock_irqsave(&counter->lock, flags);
+ if (counter->mite_chan)
+ mite_dma_disarm(counter->mite_chan);
+ spin_unlock_irqrestore(&counter->lock, flags);
+ ni_tio_configure_dma(counter, false, false);
+
+ ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx),
+ GI_GATE_INTERRUPT_ENABLE(cidx), 0x0);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ni_tio_cancel);
+
+ /* During buffered input counter operation for e-series, the gate
+ interrupt is acked automatically by the dma controller, due to the
+ Gi_Read/Write_Acknowledges_IRQ bits in the input select register. */
+static int should_ack_gate(struct ni_gpct *counter)
+{
+ unsigned long flags;
+ int retval = 0;
+
+ switch (counter->counter_dev->variant) {
+ case ni_gpct_variant_m_series:
+ /* not sure if 660x really supports gate
+ interrupts (the bits are not listed
+ in register-level manual) */
+ case ni_gpct_variant_660x:
+ return 1;
+ case ni_gpct_variant_e_series:
+ spin_lock_irqsave(&counter->lock, flags);
+ {
+ if (!counter->mite_chan ||
+ counter->mite_chan->dir != COMEDI_INPUT ||
+ (mite_done(counter->mite_chan))) {
+ retval = 1;
+ }
+ }
+ spin_unlock_irqrestore(&counter->lock, flags);
+ break;
+ }
+ return retval;
+}
+
+static void ni_tio_acknowledge_and_confirm(struct ni_gpct *counter,
+ int *gate_error,
+ int *tc_error,
+ int *perm_stale_data,
+ int *stale_data)
+{
+ unsigned cidx = counter->counter_index;
+ const unsigned short gxx_status = read_register(counter,
+ NITIO_SHARED_STATUS_REG(cidx));
+ const unsigned short gi_status = read_register(counter,
+ NITIO_STATUS_REG(cidx));
+ unsigned ack = 0;
+
+ if (gate_error)
+ *gate_error = 0;
+ if (tc_error)
+ *tc_error = 0;
+ if (perm_stale_data)
+ *perm_stale_data = 0;
+ if (stale_data)
+ *stale_data = 0;
+
+ if (gxx_status & GI_GATE_ERROR(cidx)) {
+ ack |= GI_GATE_ERROR_CONFIRM(cidx);
+ if (gate_error) {
+ /*660x don't support automatic acknowledgment
+ of gate interrupt via dma read/write
+ and report bogus gate errors */
+ if (counter->counter_dev->variant !=
+ ni_gpct_variant_660x)
+ *gate_error = 1;
+ }
+ }
+ if (gxx_status & GI_TC_ERROR(cidx)) {
+ ack |= GI_TC_ERROR_CONFIRM(cidx);
+ if (tc_error)
+ *tc_error = 1;
+ }
+ if (gi_status & GI_TC)
+ ack |= GI_TC_INTERRUPT_ACK;
+ if (gi_status & GI_GATE_INTERRUPT) {
+ if (should_ack_gate(counter))
+ ack |= GI_GATE_INTERRUPT_ACK;
+ }
+ if (ack)
+ write_register(counter, ack, NITIO_INT_ACK_REG(cidx));
+ if (ni_tio_get_soft_copy(counter, NITIO_MODE_REG(cidx)) &
+ GI_LOADING_ON_GATE) {
+ if (gxx_status & GI_STALE_DATA(cidx)) {
+ if (stale_data)
+ *stale_data = 1;
+ }
+ if (read_register(counter, NITIO_STATUS2_REG(cidx)) &
+ GI_PERMANENT_STALE(cidx)) {
+ dev_info(counter->counter_dev->dev->class_dev,
+ "%s: Gi_Permanent_Stale_Data detected.\n",
+ __func__);
+ if (perm_stale_data)
+ *perm_stale_data = 1;
+ }
+ }
+}
+
+void ni_tio_acknowledge(struct ni_gpct *counter)
+{
+ ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(ni_tio_acknowledge);
+
+void ni_tio_handle_interrupt(struct ni_gpct *counter,
+ struct comedi_subdevice *s)
+{
+ unsigned cidx = counter->counter_index;
+ unsigned gpct_mite_status;
+ unsigned long flags;
+ int gate_error;
+ int tc_error;
+ int perm_stale_data;
+
+ ni_tio_acknowledge_and_confirm(counter, &gate_error, &tc_error,
+ &perm_stale_data, NULL);
+ if (gate_error) {
+ dev_notice(counter->counter_dev->dev->class_dev,
+ "%s: Gi_Gate_Error detected.\n", __func__);
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+ if (perm_stale_data)
+ s->async->events |= COMEDI_CB_ERROR;
+ switch (counter->counter_dev->variant) {
+ case ni_gpct_variant_m_series:
+ case ni_gpct_variant_660x:
+ if (read_register(counter, NITIO_DMA_STATUS_REG(cidx)) &
+ GI_DRQ_ERROR) {
+ dev_notice(counter->counter_dev->dev->class_dev,
+ "%s: Gi_DRQ_Error detected.\n", __func__);
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+ break;
+ case ni_gpct_variant_e_series:
+ break;
+ }
+ spin_lock_irqsave(&counter->lock, flags);
+ if (!counter->mite_chan) {
+ spin_unlock_irqrestore(&counter->lock, flags);
+ return;
+ }
+ gpct_mite_status = mite_get_status(counter->mite_chan);
+ if (gpct_mite_status & CHSR_LINKC)
+ writel(CHOR_CLRLC,
+ counter->mite_chan->mite->mite_io_addr +
+ MITE_CHOR(counter->mite_chan->channel));
+ mite_sync_input_dma(counter->mite_chan, s);
+ spin_unlock_irqrestore(&counter->lock, flags);
+}
+EXPORT_SYMBOL_GPL(ni_tio_handle_interrupt);
+
+void ni_tio_set_mite_channel(struct ni_gpct *counter,
+ struct mite_channel *mite_chan)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&counter->lock, flags);
+ counter->mite_chan = mite_chan;
+ spin_unlock_irqrestore(&counter->lock, flags);
+}
+EXPORT_SYMBOL_GPL(ni_tio_set_mite_channel);
+
+static int __init ni_tiocmd_init_module(void)
+{
+ return 0;
+}
+module_init(ni_tiocmd_init_module);
+
+static void __exit ni_tiocmd_cleanup_module(void)
+{
+}
+module_exit(ni_tiocmd_cleanup_module);
+
+MODULE_AUTHOR("Comedi <comedi@comedi.org>");
+MODULE_DESCRIPTION("Comedi command support for NI general-purpose counters");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_usb6501.c b/drivers/staging/comedi/drivers/ni_usb6501.c
new file mode 100644
index 000000000..5f649f88d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_usb6501.c
@@ -0,0 +1,620 @@
+/*
+ * comedi/drivers/ni_usb6501.c
+ * Comedi driver for National Instruments USB-6501
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2014 Luca Ellero <luca.ellero@brickedbrain.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_usb6501
+ * Description: National Instruments USB-6501 module
+ * Devices: [National Instruments] USB-6501 (ni_usb6501)
+ * Author: Luca Ellero <luca.ellero@brickedbrain.com>
+ * Updated: 8 Sep 2014
+ * Status: works
+ *
+ *
+ * Configuration Options:
+ * none
+ */
+
+/*
+ * NI-6501 - USB PROTOCOL DESCRIPTION
+ *
+ * Every command is composed by two USB packets:
+ * - request (out)
+ * - response (in)
+ *
+ * Every packet is at least 12 bytes long, here is the meaning of
+ * every field (all values are hex):
+ *
+ * byte 0 is always 00
+ * byte 1 is always 01
+ * byte 2 is always 00
+ * byte 3 is the total packet length
+ *
+ * byte 4 is always 00
+ * byte 5 is is the total packet length - 4
+ * byte 6 is always 01
+ * byte 7 is the command
+ *
+ * byte 8 is 02 (request) or 00 (response)
+ * byte 9 is 00 (response) or 10 (port request) or 20 (counter request)
+ * byte 10 is always 00
+ * byte 11 is 00 (request) or 02 (response)
+ *
+ * PORT PACKETS
+ *
+ * CMD: 0xE READ_PORT
+ * REQ: 00 01 00 10 00 0C 01 0E 02 10 00 00 00 03 <PORT> 00
+ * RES: 00 01 00 10 00 0C 01 00 00 00 00 02 00 03 <BMAP> 00
+ *
+ * CMD: 0xF WRITE_PORT
+ * REQ: 00 01 00 14 00 10 01 0F 02 10 00 00 00 03 <PORT> 00 03 <BMAP> 00 00
+ * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
+ *
+ * CMD: 0x12 SET_PORT_DIR (0 = input, 1 = output)
+ * REQ: 00 01 00 18 00 14 01 12 02 10 00 00
+ * 00 05 <PORT 0> <PORT 1> <PORT 2> 00 05 00 00 00 00 00
+ * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
+ *
+ * COUNTER PACKETS
+ *
+ * CMD 0x9: START_COUNTER
+ * REQ: 00 01 00 0C 00 08 01 09 02 20 00 00
+ * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
+ *
+ * CMD 0xC: STOP_COUNTER
+ * REQ: 00 01 00 0C 00 08 01 0C 02 20 00 00
+ * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
+ *
+ * CMD 0xE: READ_COUNTER
+ * REQ: 00 01 00 0C 00 08 01 0E 02 20 00 00
+ * RES: 00 01 00 10 00 0C 01 00 00 00 00 02 <u32 counter value, Big Endian>
+ *
+ * CMD 0xF: WRITE_COUNTER
+ * REQ: 00 01 00 10 00 0C 01 0F 02 20 00 00 <u32 counter value, Big Endian>
+ * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
+ *
+ *
+ * Please visit http://www.brickedbrain.com if you need
+ * additional information or have any questions.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "../comedi_usb.h"
+
+#define NI6501_TIMEOUT 1000
+
+/* Port request packets */
+static const u8 READ_PORT_REQUEST[] = {0x00, 0x01, 0x00, 0x10,
+ 0x00, 0x0C, 0x01, 0x0E,
+ 0x02, 0x10, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x00};
+
+static const u8 WRITE_PORT_REQUEST[] = {0x00, 0x01, 0x00, 0x14,
+ 0x00, 0x10, 0x01, 0x0F,
+ 0x02, 0x10, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00};
+
+static const u8 SET_PORT_DIR_REQUEST[] = {0x00, 0x01, 0x00, 0x18,
+ 0x00, 0x14, 0x01, 0x12,
+ 0x02, 0x10, 0x00, 0x00,
+ 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+/* Counter request packets */
+static const u8 START_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C,
+ 0x00, 0x08, 0x01, 0x09,
+ 0x02, 0x20, 0x00, 0x00};
+
+static const u8 STOP_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C,
+ 0x00, 0x08, 0x01, 0x0C,
+ 0x02, 0x20, 0x00, 0x00};
+
+static const u8 READ_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C,
+ 0x00, 0x08, 0x01, 0x0E,
+ 0x02, 0x20, 0x00, 0x00};
+
+static const u8 WRITE_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x10,
+ 0x00, 0x0C, 0x01, 0x0F,
+ 0x02, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+/* Response packets */
+static const u8 GENERIC_RESPONSE[] = {0x00, 0x01, 0x00, 0x0C,
+ 0x00, 0x08, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x02};
+
+static const u8 READ_PORT_RESPONSE[] = {0x00, 0x01, 0x00, 0x10,
+ 0x00, 0x0C, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 0x00, 0x00};
+
+static const u8 READ_COUNTER_RESPONSE[] = {0x00, 0x01, 0x00, 0x10,
+ 0x00, 0x0C, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00};
+
+enum commands {
+ READ_PORT,
+ WRITE_PORT,
+ SET_PORT_DIR,
+ START_COUNTER,
+ STOP_COUNTER,
+ READ_COUNTER,
+ WRITE_COUNTER
+};
+
+struct ni6501_private {
+ struct usb_endpoint_descriptor *ep_rx;
+ struct usb_endpoint_descriptor *ep_tx;
+ struct semaphore sem;
+ u8 *usb_rx_buf;
+ u8 *usb_tx_buf;
+};
+
+static int ni6501_port_command(struct comedi_device *dev, int command,
+ const u8 *port, u8 *bitmap)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct ni6501_private *devpriv = dev->private;
+ int request_size, response_size;
+ u8 *tx = devpriv->usb_tx_buf;
+ int ret;
+
+ if (command != SET_PORT_DIR && !bitmap)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ switch (command) {
+ case READ_PORT:
+ request_size = sizeof(READ_PORT_REQUEST);
+ response_size = sizeof(READ_PORT_RESPONSE);
+ memcpy(tx, READ_PORT_REQUEST, request_size);
+ tx[14] = port[0];
+ break;
+ case WRITE_PORT:
+ request_size = sizeof(WRITE_PORT_REQUEST);
+ response_size = sizeof(GENERIC_RESPONSE);
+ memcpy(tx, WRITE_PORT_REQUEST, request_size);
+ tx[14] = port[0];
+ tx[17] = bitmap[0];
+ break;
+ case SET_PORT_DIR:
+ request_size = sizeof(SET_PORT_DIR_REQUEST);
+ response_size = sizeof(GENERIC_RESPONSE);
+ memcpy(tx, SET_PORT_DIR_REQUEST, request_size);
+ tx[14] = port[0];
+ tx[15] = port[1];
+ tx[16] = port[2];
+ break;
+ default:
+ ret = -EINVAL;
+ goto end;
+ }
+
+ ret = usb_bulk_msg(usb,
+ usb_sndbulkpipe(usb,
+ devpriv->ep_tx->bEndpointAddress),
+ devpriv->usb_tx_buf,
+ request_size,
+ NULL,
+ NI6501_TIMEOUT);
+ if (ret)
+ goto end;
+
+ ret = usb_bulk_msg(usb,
+ usb_rcvbulkpipe(usb,
+ devpriv->ep_rx->bEndpointAddress),
+ devpriv->usb_rx_buf,
+ response_size,
+ NULL,
+ NI6501_TIMEOUT);
+ if (ret)
+ goto end;
+
+ /* Check if results are valid */
+
+ if (command == READ_PORT) {
+ bitmap[0] = devpriv->usb_rx_buf[14];
+ /* mask bitmap for comparing */
+ devpriv->usb_rx_buf[14] = 0x00;
+
+ if (memcmp(devpriv->usb_rx_buf, READ_PORT_RESPONSE,
+ sizeof(READ_PORT_RESPONSE))) {
+ ret = -EINVAL;
+ }
+ } else if (memcmp(devpriv->usb_rx_buf, GENERIC_RESPONSE,
+ sizeof(GENERIC_RESPONSE))) {
+ ret = -EINVAL;
+ }
+end:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int ni6501_counter_command(struct comedi_device *dev, int command,
+ u32 *val)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct ni6501_private *devpriv = dev->private;
+ int request_size, response_size;
+ u8 *tx = devpriv->usb_tx_buf;
+ int ret;
+
+ if ((command == READ_COUNTER || command == WRITE_COUNTER) && !val)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ switch (command) {
+ case START_COUNTER:
+ request_size = sizeof(START_COUNTER_REQUEST);
+ response_size = sizeof(GENERIC_RESPONSE);
+ memcpy(tx, START_COUNTER_REQUEST, request_size);
+ break;
+ case STOP_COUNTER:
+ request_size = sizeof(STOP_COUNTER_REQUEST);
+ response_size = sizeof(GENERIC_RESPONSE);
+ memcpy(tx, STOP_COUNTER_REQUEST, request_size);
+ break;
+ case READ_COUNTER:
+ request_size = sizeof(READ_COUNTER_REQUEST);
+ response_size = sizeof(READ_COUNTER_RESPONSE);
+ memcpy(tx, READ_COUNTER_REQUEST, request_size);
+ break;
+ case WRITE_COUNTER:
+ request_size = sizeof(WRITE_COUNTER_REQUEST);
+ response_size = sizeof(GENERIC_RESPONSE);
+ memcpy(tx, WRITE_COUNTER_REQUEST, request_size);
+ /* Setup tx packet: bytes 12,13,14,15 hold the */
+ /* u32 counter value (Big Endian) */
+ *((__be32 *)&tx[12]) = cpu_to_be32(*val);
+ break;
+ default:
+ ret = -EINVAL;
+ goto end;
+ }
+
+ ret = usb_bulk_msg(usb,
+ usb_sndbulkpipe(usb,
+ devpriv->ep_tx->bEndpointAddress),
+ devpriv->usb_tx_buf,
+ request_size,
+ NULL,
+ NI6501_TIMEOUT);
+ if (ret)
+ goto end;
+
+ ret = usb_bulk_msg(usb,
+ usb_rcvbulkpipe(usb,
+ devpriv->ep_rx->bEndpointAddress),
+ devpriv->usb_rx_buf,
+ response_size,
+ NULL,
+ NI6501_TIMEOUT);
+ if (ret)
+ goto end;
+
+ /* Check if results are valid */
+
+ if (command == READ_COUNTER) {
+ int i;
+
+ /* Read counter value: bytes 12,13,14,15 of rx packet */
+ /* hold the u32 counter value (Big Endian) */
+ *val = be32_to_cpu(*((__be32 *)&devpriv->usb_rx_buf[12]));
+
+ /* mask counter value for comparing */
+ for (i = 12; i < sizeof(READ_COUNTER_RESPONSE); ++i)
+ devpriv->usb_rx_buf[i] = 0x00;
+
+ if (memcmp(devpriv->usb_rx_buf, READ_COUNTER_RESPONSE,
+ sizeof(READ_COUNTER_RESPONSE))) {
+ ret = -EINVAL;
+ }
+ } else if (memcmp(devpriv->usb_rx_buf, GENERIC_RESPONSE,
+ sizeof(GENERIC_RESPONSE))) {
+ ret = -EINVAL;
+ }
+end:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int ni6501_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ u8 port[3];
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ port[0] = (s->io_bits) & 0xff;
+ port[1] = (s->io_bits >> 8) & 0xff;
+ port[2] = (s->io_bits >> 16) & 0xff;
+
+ ret = ni6501_port_command(dev, SET_PORT_DIR, port, NULL);
+ if (ret)
+ return ret;
+
+ return insn->n;
+}
+
+static int ni6501_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ int ret;
+ u8 port;
+ u8 bitmap;
+
+ mask = comedi_dio_update_state(s, data);
+
+ for (port = 0; port < 3; port++) {
+ if (mask & (0xFF << port * 8)) {
+ bitmap = (s->state >> port * 8) & 0xFF;
+ ret = ni6501_port_command(dev, WRITE_PORT,
+ &port, &bitmap);
+ if (ret)
+ return ret;
+ }
+ }
+
+ data[1] = 0;
+
+ for (port = 0; port < 3; port++) {
+ ret = ni6501_port_command(dev, READ_PORT, &port, &bitmap);
+ if (ret)
+ return ret;
+ data[1] |= bitmap << port * 8;
+ }
+
+ return insn->n;
+}
+
+static int ni6501_cnt_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ u32 val = 0;
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ ret = ni6501_counter_command(dev, START_COUNTER, NULL);
+ break;
+ case INSN_CONFIG_DISARM:
+ ret = ni6501_counter_command(dev, STOP_COUNTER, NULL);
+ break;
+ case INSN_CONFIG_RESET:
+ ret = ni6501_counter_command(dev, STOP_COUNTER, NULL);
+ if (ret)
+ break;
+ ret = ni6501_counter_command(dev, WRITE_COUNTER, &val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ? ret : insn->n;
+}
+
+static int ni6501_cnt_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ u32 val;
+ unsigned int i;
+
+ for (i = 0; i < insn->n; i++) {
+ ret = ni6501_counter_command(dev, READ_COUNTER, &val);
+ if (ret)
+ return ret;
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int ni6501_cnt_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ if (insn->n) {
+ u32 val = data[insn->n - 1];
+
+ ret = ni6501_counter_command(dev, WRITE_COUNTER, &val);
+ if (ret)
+ return ret;
+ }
+
+ return insn->n;
+}
+
+static int ni6501_alloc_usb_buffers(struct comedi_device *dev)
+{
+ struct ni6501_private *devpriv = dev->private;
+ size_t size;
+
+ size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize);
+ devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
+ if (!devpriv->usb_rx_buf)
+ return -ENOMEM;
+
+ size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+ devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
+ if (!devpriv->usb_tx_buf) {
+ kfree(devpriv->usb_rx_buf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int ni6501_find_endpoints(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct ni6501_private *devpriv = dev->private;
+ struct usb_host_interface *iface_desc = intf->cur_altsetting;
+ struct usb_endpoint_descriptor *ep_desc;
+ int i;
+
+ if (iface_desc->desc.bNumEndpoints != 2) {
+ dev_err(dev->class_dev, "Wrong number of endpoints\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+ ep_desc = &iface_desc->endpoint[i].desc;
+
+ if (usb_endpoint_is_bulk_in(ep_desc)) {
+ if (!devpriv->ep_rx)
+ devpriv->ep_rx = ep_desc;
+ continue;
+ }
+
+ if (usb_endpoint_is_bulk_out(ep_desc)) {
+ if (!devpriv->ep_tx)
+ devpriv->ep_tx = ep_desc;
+ continue;
+ }
+ }
+
+ if (!devpriv->ep_rx || !devpriv->ep_tx)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int ni6501_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct ni6501_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = ni6501_find_endpoints(dev);
+ if (ret)
+ return ret;
+
+ ret = ni6501_alloc_usb_buffers(dev);
+ if (ret)
+ return ret;
+
+ sema_init(&devpriv->sem, 1);
+ usb_set_intfdata(intf, devpriv);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Digital Input/Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni6501_dio_insn_bits;
+ s->insn_config = ni6501_dio_insn_config;
+
+ /* Counter subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
+ s->n_chan = 1;
+ s->maxdata = 0xffffffff;
+ s->insn_read = ni6501_cnt_insn_read;
+ s->insn_write = ni6501_cnt_insn_write;
+ s->insn_config = ni6501_cnt_insn_config;
+
+ return 0;
+}
+
+static void ni6501_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct ni6501_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ usb_set_intfdata(intf, NULL);
+
+ kfree(devpriv->usb_rx_buf);
+ kfree(devpriv->usb_tx_buf);
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver ni6501_driver = {
+ .module = THIS_MODULE,
+ .driver_name = "ni6501",
+ .auto_attach = ni6501_auto_attach,
+ .detach = ni6501_detach,
+};
+
+static int ni6501_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &ni6501_driver, id->driver_info);
+}
+
+static const struct usb_device_id ni6501_usb_table[] = {
+ { USB_DEVICE(0x3923, 0x718a) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, ni6501_usb_table);
+
+static struct usb_driver ni6501_usb_driver = {
+ .name = "ni6501",
+ .id_table = ni6501_usb_table,
+ .probe = ni6501_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+};
+module_comedi_usb_driver(ni6501_driver, ni6501_usb_driver);
+
+MODULE_AUTHOR("Luca Ellero");
+MODULE_DESCRIPTION("Comedi driver for National Instruments USB-6501");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl711.c b/drivers/staging/comedi/drivers/pcl711.c
new file mode 100644
index 000000000..cfc3a627d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl711.c
@@ -0,0 +1,521 @@
+/*
+ * pcl711.c
+ * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ * Janne Jalkanen <jalkanen@cs.hut.fi>
+ * Eric Bunn <ebu@cs.hut.fi>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: pcl711
+ * Description: Advantech PCL-711 and 711b, ADLink ACL-8112
+ * Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
+ * [ADLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
+ * Author: David A. Schleef <ds@schleef.org>
+ * Janne Jalkanen <jalkanen@cs.hut.fi>
+ * Eric Bunn <ebu@cs.hut.fi>
+ * Updated:
+ * Status: mostly complete
+ *
+ * Configuration Options:
+ * [0] - I/O port base
+ * [1] - IRQ, optional
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_8254.h"
+
+/*
+ * I/O port register map
+ */
+#define PCL711_TIMER_BASE 0x00
+#define PCL711_AI_LSB_REG 0x04
+#define PCL711_AI_MSB_REG 0x05
+#define PCL711_AI_MSB_DRDY (1 << 4)
+#define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define PCL711_DI_LSB_REG 0x06
+#define PCL711_DI_MSB_REG 0x07
+#define PCL711_INT_STAT_REG 0x08
+#define PCL711_INT_STAT_CLR (0 << 0) /* any value will work */
+#define PCL711_AI_GAIN_REG 0x09
+#define PCL711_AI_GAIN(x) (((x) & 0xf) << 0)
+#define PCL711_MUX_REG 0x0a
+#define PCL711_MUX_CHAN(x) (((x) & 0xf) << 0)
+#define PCL711_MUX_CS0 (1 << 4)
+#define PCL711_MUX_CS1 (1 << 5)
+#define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1)
+#define PCL711_MODE_REG 0x0b
+#define PCL711_MODE_DEFAULT (0 << 0)
+#define PCL711_MODE_SOFTTRIG (1 << 0)
+#define PCL711_MODE_EXT (2 << 0)
+#define PCL711_MODE_EXT_IRQ (3 << 0)
+#define PCL711_MODE_PACER (4 << 0)
+#define PCL711_MODE_PACER_IRQ (6 << 0)
+#define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4)
+#define PCL711_SOFTTRIG_REG 0x0c
+#define PCL711_SOFTTRIG (0 << 0) /* any value will work */
+#define PCL711_DO_LSB_REG 0x0d
+#define PCL711_DO_MSB_REG 0x0e
+
+static const struct comedi_lrange range_pcl711b_ai = {
+ 5, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(0.3125)
+ }
+};
+
+static const struct comedi_lrange range_acl8112hg_ai = {
+ 12, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.005),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01),
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_acl8112dg_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ BIP_RANGE(10)
+ }
+};
+
+struct pcl711_board {
+ const char *name;
+ int n_aichan;
+ int n_aochan;
+ int maxirq;
+ const struct comedi_lrange *ai_range_type;
+};
+
+static const struct pcl711_board boardtypes[] = {
+ {
+ .name = "pcl711",
+ .n_aichan = 8,
+ .n_aochan = 1,
+ .ai_range_type = &range_bipolar5,
+ }, {
+ .name = "pcl711b",
+ .n_aichan = 8,
+ .n_aochan = 1,
+ .maxirq = 7,
+ .ai_range_type = &range_pcl711b_ai,
+ }, {
+ .name = "acl8112hg",
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .maxirq = 15,
+ .ai_range_type = &range_acl8112hg_ai,
+ }, {
+ .name = "acl8112dg",
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .maxirq = 15,
+ .ai_range_type = &range_acl8112dg_ai,
+ },
+};
+
+static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
+{
+ /*
+ * The pcl711b board uses bits in the mode register to select the
+ * interrupt. The other boards supported by this driver all use
+ * jumpers on the board.
+ *
+ * Enables the interrupt when needed on the pcl711b board. These
+ * bits do nothing on the other boards.
+ */
+ if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
+ mode |= PCL711_MODE_IRQ(dev->irq);
+
+ outb(mode, dev->iobase + PCL711_MODE_REG);
+}
+
+static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL711_AI_LSB_REG);
+
+ return val & s->maxdata;
+}
+
+static int pcl711_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+ pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
+ return 0;
+}
+
+static irqreturn_t pcl711_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int data;
+
+ if (!dev->attached) {
+ dev_err(dev->class_dev, "spurious interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ data = pcl711_ai_get_sample(dev, s);
+
+ outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+
+ comedi_buf_write_samples(s, &data, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static void pcl711_set_changain(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec)
+{
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned int mux = 0;
+
+ outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
+
+ if (s->n_chan > 8) {
+ /* Select the correct MPC508A chip */
+ if (aref == AREF_DIFF) {
+ chan &= 0x7;
+ mux |= PCL711_MUX_DIFF;
+ } else {
+ if (chan < 8)
+ mux |= PCL711_MUX_CS0;
+ else
+ mux |= PCL711_MUX_CS1;
+ }
+ }
+ outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
+}
+
+static int pcl711_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCL711_AI_MSB_REG);
+ if ((status & PCL711_AI_MSB_DRDY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcl711_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ int i;
+
+ pcl711_set_changain(dev, s, insn->chanspec);
+
+ pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
+
+ for (i = 0; i < insn->n; i++) {
+ outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = pcl711_ai_get_sample(dev, s);
+ }
+
+ return insn->n;
+}
+
+static int pcl711_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_EXT) {
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ } else {
+#define MAX_SPEED 1000
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4 */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ unsigned int arg = cmd->scan_begin_arg;
+
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ pcl711_set_changain(dev, s, cmd->chanlist[0]);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+ pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
+ } else {
+ pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
+ }
+
+ return 0;
+}
+
+static void pcl711_ao_write(struct comedi_device *dev,
+ unsigned int chan, unsigned int val)
+{
+ outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
+ outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
+}
+
+static int pcl711_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ pcl711_ao_write(dev, chan, val);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pcl711_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL711_DI_LSB_REG);
+ val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int pcl711_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
+ if (mask & 0xff00)
+ outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl711_board *board = dev->board_ptr;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ if (it->options[1] && it->options[1] <= board->maxirq) {
+ ret = request_irq(it->options[1], pcl711_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ dev->pacer = comedi_8254_init(dev->iobase + PCL711_TIMER_BASE,
+ I8254_OSC_BASE_2MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ if (board->n_aichan > 8)
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = board->n_aichan;
+ s->maxdata = 0xfff;
+ s->range_table = board->ai_range_type;
+ s->insn_read = pcl711_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->do_cmdtest = pcl711_ai_cmdtest;
+ s->do_cmd = pcl711_ai_cmd;
+ s->cancel = pcl711_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_aochan;
+ s->maxdata = 0xfff;
+ s->range_table = &range_bipolar5;
+ s->insn_write = pcl711_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl711_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl711_do_insn_bits;
+
+ /* clear DAC */
+ pcl711_ao_write(dev, 0, 0x0);
+ pcl711_ao_write(dev, 1, 0x0);
+
+ return 0;
+}
+
+static struct comedi_driver pcl711_driver = {
+ .driver_name = "pcl711",
+ .module = THIS_MODULE,
+ .attach = pcl711_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl711_board),
+};
+module_comedi_driver(pcl711_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl724.c b/drivers/staging/comedi/drivers/pcl724.c
new file mode 100644
index 000000000..74b07e174
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl724.c
@@ -0,0 +1,152 @@
+/*
+ * pcl724.c
+ * Comedi driver for 8255 based ISA and PC/104 DIO boards
+ *
+ * Michal Dobes <dobes@tesnet.cz>
+ */
+
+/*
+ * Driver: pcl724
+ * Description: Comedi driver for 8255 based ISA DIO boards
+ * Devices: [Advantech] PCL-724 (pcl724), PCL-722 (pcl722), PCL-731 (pcl731),
+ * [ADLink] ACL-7122 (acl7122), ACL-7124 (acl7124), PET-48DIO (pet48dio),
+ * [WinSystems] PCM-IO48 (pcmio48),
+ * [Diamond Systems] ONYX-MM-DIO (onyx-mm-dio)
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ * Status: untested
+ *
+ * Configuration options:
+ * [0] - IO Base
+ * [1] - IRQ (not supported)
+ * [2] - number of DIO (pcl722 and acl7122 boards)
+ * 0, 144: 144 DIO configuration
+ * 1, 96: 96 DIO configuration
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+
+struct pcl724_board {
+ const char *name;
+ unsigned int io_range;
+ unsigned int can_have96:1;
+ unsigned int is_pet48:1;
+ int numofports;
+};
+
+static const struct pcl724_board boardtypes[] = {
+ {
+ .name = "pcl724",
+ .io_range = 0x04,
+ .numofports = 1, /* 24 DIO channels */
+ }, {
+ .name = "pcl722",
+ .io_range = 0x20,
+ .can_have96 = 1,
+ .numofports = 6, /* 144 (or 96) DIO channels */
+ }, {
+ .name = "pcl731",
+ .io_range = 0x08,
+ .numofports = 2, /* 48 DIO channels */
+ }, {
+ .name = "acl7122",
+ .io_range = 0x20,
+ .can_have96 = 1,
+ .numofports = 6, /* 144 (or 96) DIO channels */
+ }, {
+ .name = "acl7124",
+ .io_range = 0x04,
+ .numofports = 1, /* 24 DIO channels */
+ }, {
+ .name = "pet48dio",
+ .io_range = 0x02,
+ .is_pet48 = 1,
+ .numofports = 2, /* 48 DIO channels */
+ }, {
+ .name = "pcmio48",
+ .io_range = 0x08,
+ .numofports = 2, /* 48 DIO channels */
+ }, {
+ .name = "onyx-mm-dio",
+ .io_range = 0x10,
+ .numofports = 2, /* 48 DIO channels */
+ },
+};
+
+static int pcl724_8255mapped_io(struct comedi_device *dev,
+ int dir, int port, int data,
+ unsigned long iobase)
+{
+ int movport = I8255_SIZE * (iobase >> 12);
+
+ iobase &= 0x0fff;
+
+ outb(port + movport, iobase);
+ if (dir) {
+ outb(data, iobase + 1);
+ return 0;
+ }
+ return inb(iobase + 1);
+}
+
+static int pcl724_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pcl724_board *board = dev->board_ptr;
+ struct comedi_subdevice *s;
+ unsigned long iobase;
+ unsigned int iorange;
+ int n_subdevices;
+ int ret;
+ int i;
+
+ iorange = board->io_range;
+ n_subdevices = board->numofports;
+
+ /* Handle PCL-724 in 96 DIO configuration */
+ if (board->can_have96 &&
+ (it->options[2] == 1 || it->options[2] == 96)) {
+ iorange = 0x10;
+ n_subdevices = 4;
+ }
+
+ ret = comedi_request_region(dev, it->options[0], iorange);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (board->is_pet48) {
+ iobase = dev->iobase + (i * 0x1000);
+ ret = subdev_8255_init(dev, s, pcl724_8255mapped_io,
+ iobase);
+ } else {
+ ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE);
+ }
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver pcl724_driver = {
+ .driver_name = "pcl724",
+ .module = THIS_MODULE,
+ .attach = pcl724_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl724_board),
+};
+module_comedi_driver(pcl724_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for 8255 based ISA and PC/104 DIO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl726.c b/drivers/staging/comedi/drivers/pcl726.c
new file mode 100644
index 000000000..256850ccb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl726.c
@@ -0,0 +1,432 @@
+/*
+ * pcl726.c
+ * Comedi driver for 6/12-Channel D/A Output and DIO cards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: pcl726
+ * Description: Advantech PCL-726 & compatibles
+ * Author: David A. Schleef <ds@schleef.org>
+ * Status: untested
+ * Devices: [Advantech] PCL-726 (pcl726), PCL-727 (pcl727), PCL-728 (pcl728),
+ * [ADLink] ACL-6126 (acl6126), ACL-6128 (acl6128)
+ *
+ * Configuration Options:
+ * [0] - IO Base
+ * [1] - IRQ (ACL-6126 only)
+ * [2] - D/A output range for channel 0
+ * [3] - D/A output range for channel 1
+ *
+ * Boards with > 2 analog output channels:
+ * [4] - D/A output range for channel 2
+ * [5] - D/A output range for channel 3
+ * [6] - D/A output range for channel 4
+ * [7] - D/A output range for channel 5
+ *
+ * Boards with > 6 analog output channels:
+ * [8] - D/A output range for channel 6
+ * [9] - D/A output range for channel 7
+ * [10] - D/A output range for channel 8
+ * [11] - D/A output range for channel 9
+ * [12] - D/A output range for channel 10
+ * [13] - D/A output range for channel 11
+ *
+ * For PCL-726 the D/A output ranges are:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: unknown
+ *
+ * For PCL-727:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: 4-20mA
+ *
+ * For PCL-728 and ACL-6128:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: 0-20mA
+ *
+ * For ACL-6126:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#define PCL726_AO_MSB_REG(x) (0x00 + ((x) * 2))
+#define PCL726_AO_LSB_REG(x) (0x01 + ((x) * 2))
+#define PCL726_DO_MSB_REG 0x0c
+#define PCL726_DO_LSB_REG 0x0d
+#define PCL726_DI_MSB_REG 0x0e
+#define PCL726_DI_LSB_REG 0x0f
+
+#define PCL727_DI_MSB_REG 0x00
+#define PCL727_DI_LSB_REG 0x01
+#define PCL727_DO_MSB_REG 0x18
+#define PCL727_DO_LSB_REG 0x19
+
+static const struct comedi_lrange *const rangelist_726[] = {
+ &range_unipolar5,
+ &range_unipolar10,
+ &range_bipolar5,
+ &range_bipolar10,
+ &range_4_20mA,
+ &range_unknown
+};
+
+static const struct comedi_lrange *const rangelist_727[] = {
+ &range_unipolar5,
+ &range_unipolar10,
+ &range_bipolar5,
+ &range_4_20mA
+};
+
+static const struct comedi_lrange *const rangelist_728[] = {
+ &range_unipolar5,
+ &range_unipolar10,
+ &range_bipolar5,
+ &range_bipolar10,
+ &range_4_20mA,
+ &range_0_20mA
+};
+
+struct pcl726_board {
+ const char *name;
+ unsigned long io_len;
+ unsigned int irq_mask;
+ const struct comedi_lrange *const *ao_ranges;
+ int ao_num_ranges;
+ int ao_nchan;
+ unsigned int have_dio:1;
+ unsigned int is_pcl727:1;
+};
+
+static const struct pcl726_board pcl726_boards[] = {
+ {
+ .name = "pcl726",
+ .io_len = 0x10,
+ .ao_ranges = &rangelist_726[0],
+ .ao_num_ranges = ARRAY_SIZE(rangelist_726),
+ .ao_nchan = 6,
+ .have_dio = 1,
+ }, {
+ .name = "pcl727",
+ .io_len = 0x20,
+ .ao_ranges = &rangelist_727[0],
+ .ao_num_ranges = ARRAY_SIZE(rangelist_727),
+ .ao_nchan = 12,
+ .have_dio = 1,
+ .is_pcl727 = 1,
+ }, {
+ .name = "pcl728",
+ .io_len = 0x08,
+ .ao_num_ranges = ARRAY_SIZE(rangelist_728),
+ .ao_ranges = &rangelist_728[0],
+ .ao_nchan = 2,
+ }, {
+ .name = "acl6126",
+ .io_len = 0x10,
+ .irq_mask = 0x96e8,
+ .ao_num_ranges = ARRAY_SIZE(rangelist_726),
+ .ao_ranges = &rangelist_726[0],
+ .ao_nchan = 6,
+ .have_dio = 1,
+ }, {
+ .name = "acl6128",
+ .io_len = 0x08,
+ .ao_num_ranges = ARRAY_SIZE(rangelist_728),
+ .ao_ranges = &rangelist_728[0],
+ .ao_nchan = 2,
+ },
+};
+
+struct pcl726_private {
+ const struct comedi_lrange *rangelist[12];
+ unsigned int cmd_running:1;
+};
+
+static int pcl726_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static int pcl726_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* Step 5: check channel list if it exists */
+
+ return 0;
+}
+
+static int pcl726_intr_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl726_private *devpriv = dev->private;
+
+ devpriv->cmd_running = 1;
+
+ return 0;
+}
+
+static int pcl726_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl726_private *devpriv = dev->private;
+
+ devpriv->cmd_running = 0;
+
+ return 0;
+}
+
+static irqreturn_t pcl726_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pcl726_private *devpriv = dev->private;
+
+ if (devpriv->cmd_running) {
+ pcl726_intr_cancel(dev, s);
+
+ comedi_buf_write_samples(s, &s->state, 1);
+ comedi_handle_events(dev, s);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int pcl726_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ /* bipolar data to the DAC is two's complement */
+ if (comedi_chan_range_is_bipolar(s, chan, range))
+ val = comedi_offset_munge(s, val);
+
+ /* order is important, MSB then LSB */
+ outb((val >> 8) & 0xff, dev->iobase + PCL726_AO_MSB_REG(chan));
+ outb(val & 0xff, dev->iobase + PCL726_AO_LSB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int pcl726_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct pcl726_board *board = dev->board_ptr;
+ unsigned int val;
+
+ if (board->is_pcl727) {
+ val = inb(dev->iobase + PCL727_DI_LSB_REG);
+ val |= (inb(dev->iobase + PCL727_DI_MSB_REG) << 8);
+ } else {
+ val = inb(dev->iobase + PCL726_DI_LSB_REG);
+ val |= (inb(dev->iobase + PCL726_DI_MSB_REG) << 8);
+ }
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int pcl726_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct pcl726_board *board = dev->board_ptr;
+ unsigned long io = dev->iobase;
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (board->is_pcl727) {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, io + PCL727_DO_LSB_REG);
+ if (mask & 0xff00)
+ outb((s->state >> 8), io + PCL727_DO_MSB_REG);
+ } else {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, io + PCL726_DO_LSB_REG);
+ if (mask & 0xff00)
+ outb((s->state >> 8), io + PCL726_DO_MSB_REG);
+ }
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pcl726_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pcl726_board *board = dev->board_ptr;
+ struct pcl726_private *devpriv;
+ struct comedi_subdevice *s;
+ int subdev;
+ int ret;
+ int i;
+
+ ret = comedi_request_region(dev, it->options[0], board->io_len);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /*
+ * Hook up the external trigger source interrupt only if the
+ * user config option is valid and the board supports interrupts.
+ */
+ if (it->options[1] && (board->irq_mask & (1 << it->options[1]))) {
+ ret = request_irq(it->options[1], pcl726_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ /* External trigger source is from Pin-17 of CN3 */
+ dev->irq = it->options[1];
+ }
+ }
+
+ /* setup the per-channel analog output range_table_list */
+ for (i = 0; i < 12; i++) {
+ unsigned int opt = it->options[2 + i];
+
+ if (opt < board->ao_num_ranges && i < board->ao_nchan)
+ devpriv->rangelist[i] = board->ao_ranges[opt];
+ else
+ devpriv->rangelist[i] = &range_unknown;
+ }
+
+ subdev = board->have_dio ? 3 : 1;
+ if (dev->irq)
+ subdev++;
+ ret = comedi_alloc_subdevices(dev, subdev);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = board->ao_nchan;
+ s->maxdata = 0x0fff;
+ s->range_table_list = devpriv->rangelist;
+ s->insn_write = pcl726_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ if (board->have_dio) {
+ /* Digital Input subdevice */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->insn_bits = pcl726_di_insn_bits;
+ s->range_table = &range_digital;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->insn_bits = pcl726_do_insn_bits;
+ s->range_table = &range_digital;
+ }
+
+ if (dev->irq) {
+ /* Digial Input subdevice - Interrupt support */
+ s = &dev->subdevices[subdev++];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl726_intr_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = pcl726_intr_cmdtest;
+ s->do_cmd = pcl726_intr_cmd;
+ s->cancel = pcl726_intr_cancel;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver pcl726_driver = {
+ .driver_name = "pcl726",
+ .module = THIS_MODULE,
+ .attach = pcl726_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pcl726_boards[0].name,
+ .num_names = ARRAY_SIZE(pcl726_boards),
+ .offset = sizeof(struct pcl726_board),
+};
+module_comedi_driver(pcl726_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Advantech PCL-726 & compatibles");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl730.c b/drivers/staging/comedi/drivers/pcl730.c
new file mode 100644
index 000000000..ce958eef2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl730.c
@@ -0,0 +1,349 @@
+/*
+ * comedi/drivers/pcl730.c
+ * Driver for Advantech PCL-730 and clones
+ * JosĂ© Luis SĂ¡nchez
+ */
+
+/*
+ * Driver: pcl730
+ * Description: Advantech PCL-730 (& compatibles)
+ * Devices: [Advantech] PCL-730 (pcl730), PCM-3730 (pcm3730), PCL-725 (pcl725),
+ * PCL-733 (pcl733), PCL-734 (pcl734),
+ * [ADLink] ACL-7130 (acl7130), ACL-7225b (acl7225b),
+ * [ICP] ISO-730 (iso730), P8R8-DIO (p8r8dio), P16R16-DIO (p16r16dio),
+ * [Diamond Systems] OPMM-1616-XT (opmm-1616-xt), PEARL-MM-P (pearl-mm-p),
+ * IR104-PBF (ir104-pbf),
+ * Author: JosĂ© Luis SĂ¡nchez (jsanchezv@teleline.es)
+ * Status: untested
+ *
+ * Configuration options:
+ * [0] - I/O port base
+ *
+ * Interrupts are not supported.
+ * The ACL-7130 card has an 8254 timer/counter not supported by this driver.
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/*
+ * Register map
+ *
+ * The register map varies slightly depending on the board type but
+ * all registers are 8-bit.
+ *
+ * The boardinfo 'io_range' is used to allow comedi to request the
+ * proper range required by the board.
+ *
+ * The comedi_subdevice 'private' data is used to pass the register
+ * offset to the (*insn_bits) functions to read/write the correct
+ * registers.
+ *
+ * The basic register mapping looks like this:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) / inputs 0-7 (read)
+ * BASE+1 Isolated outputs 8-15 (write) / inputs 8-15 (read)
+ * BASE+2 TTL outputs 0-7 (write) / inputs 0-7 (read)
+ * BASE+3 TTL outputs 8-15 (write) / inputs 8-15 (read)
+ *
+ * The pcm3730 board does not have register BASE+1.
+ *
+ * The pcl725 and p8r8dio only have registers BASE+0 and BASE+1:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) (read back on p8r8dio)
+ * BASE+1 Isolated inputs 0-7 (read)
+ *
+ * The acl7225b and p16r16dio boards have this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) (read back)
+ * BASE+1 Isolated outputs 8-15 (write) (read back)
+ * BASE+2 Isolated inputs 0-7 (read)
+ * BASE+3 Isolated inputs 8-15 (read)
+ *
+ * The pcl733 and pcl733 boards have this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) or inputs 0-7 (read)
+ * BASE+1 Isolated outputs 8-15 (write) or inputs 8-15 (read)
+ * BASE+2 Isolated outputs 16-23 (write) or inputs 16-23 (read)
+ * BASE+3 Isolated outputs 24-31 (write) or inputs 24-31 (read)
+ *
+ * The opmm-1616-xt board has this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) (read back)
+ * BASE+1 Isolated outputs 8-15 (write) (read back)
+ * BASE+2 Isolated inputs 0-7 (read)
+ * BASE+3 Isolated inputs 8-15 (read)
+ *
+ * These registers are not currently supported:
+ *
+ * BASE+2 Relay select register (write)
+ * BASE+3 Board reset control register (write)
+ * BASE+4 Interrupt control register (write)
+ * BASE+4 Change detect 7-0 status register (read)
+ * BASE+5 LED control register (write)
+ * BASE+5 Change detect 15-8 status register (read)
+ *
+ * The pearl-mm-p board has this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write)
+ * BASE+1 Isolated outputs 8-15 (write)
+ *
+ * The ir104-pbf board has this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) (read back)
+ * BASE+1 Isolated outputs 8-15 (write) (read back)
+ * BASE+2 Isolated outputs 16-19 (write) (read back)
+ * BASE+4 Isolated inputs 0-7 (read)
+ * BASE+5 Isolated inputs 8-15 (read)
+ * BASE+6 Isolated inputs 16-19 (read)
+ */
+
+struct pcl730_board {
+ const char *name;
+ unsigned int io_range;
+ unsigned is_pcl725:1;
+ unsigned is_acl7225b:1;
+ unsigned is_ir104:1;
+ unsigned has_readback:1;
+ unsigned has_ttl_io:1;
+ int n_subdevs;
+ int n_iso_out_chan;
+ int n_iso_in_chan;
+ int n_ttl_chan;
+};
+
+static const struct pcl730_board pcl730_boards[] = {
+ {
+ .name = "pcl730",
+ .io_range = 0x04,
+ .has_ttl_io = 1,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "iso730",
+ .io_range = 0x04,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "acl7130",
+ .io_range = 0x08,
+ .has_ttl_io = 1,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "pcm3730",
+ .io_range = 0x04,
+ .has_ttl_io = 1,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 8,
+ .n_iso_in_chan = 8,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "pcl725",
+ .io_range = 0x02,
+ .is_pcl725 = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 8,
+ .n_iso_in_chan = 8,
+ }, {
+ .name = "p8r8dio",
+ .io_range = 0x02,
+ .is_pcl725 = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 8,
+ .n_iso_in_chan = 8,
+ }, {
+ .name = "acl7225b",
+ .io_range = 0x08, /* only 4 are used */
+ .is_acl7225b = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ }, {
+ .name = "p16r16dio",
+ .io_range = 0x04,
+ .is_acl7225b = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ }, {
+ .name = "pcl733",
+ .io_range = 0x04,
+ .n_subdevs = 1,
+ .n_iso_in_chan = 32,
+ }, {
+ .name = "pcl734",
+ .io_range = 0x04,
+ .n_subdevs = 1,
+ .n_iso_out_chan = 32,
+ }, {
+ .name = "opmm-1616-xt",
+ .io_range = 0x10,
+ .is_acl7225b = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ }, {
+ .name = "pearl-mm-p",
+ .io_range = 0x02,
+ .n_subdevs = 1,
+ .n_iso_out_chan = 16,
+ }, {
+ .name = "ir104-pbf",
+ .io_range = 0x08,
+ .is_ir104 = 1,
+ .has_readback = 1,
+ .n_iso_out_chan = 20,
+ .n_iso_in_chan = 20,
+ },
+};
+
+static int pcl730_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long reg = (unsigned long)s->private;
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, dev->iobase + reg);
+ if ((mask & 0xff00) && (s->n_chan > 8))
+ outb((s->state >> 8) & 0xff, dev->iobase + reg + 1);
+ if ((mask & 0xff0000) && (s->n_chan > 16))
+ outb((s->state >> 16) & 0xff, dev->iobase + reg + 2);
+ if ((mask & 0xff000000) && (s->n_chan > 24))
+ outb((s->state >> 24) & 0xff, dev->iobase + reg + 3);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static unsigned int pcl730_get_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned long reg = (unsigned long)s->private;
+ unsigned int val;
+
+ val = inb(dev->iobase + reg);
+ if (s->n_chan > 8)
+ val |= (inb(dev->iobase + reg + 1) << 8);
+ if (s->n_chan > 16)
+ val |= (inb(dev->iobase + reg + 2) << 16);
+ if (s->n_chan > 24)
+ val |= (inb(dev->iobase + reg + 3) << 24);
+
+ return val;
+}
+
+static int pcl730_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = pcl730_get_bits(dev, s);
+
+ return insn->n;
+}
+
+static int pcl730_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pcl730_board *board = dev->board_ptr;
+ struct comedi_subdevice *s;
+ int subdev;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], board->io_range);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, board->n_subdevs);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ if (board->n_iso_out_chan) {
+ /* Isolated Digital Outputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_iso_out_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_do_insn_bits;
+ s->private = (void *)0;
+
+ /* get the initial state if supported */
+ if (board->has_readback)
+ s->state = pcl730_get_bits(dev, s);
+ }
+
+ if (board->n_iso_in_chan) {
+ /* Isolated Digital Inputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = board->n_iso_in_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_di_insn_bits;
+ s->private = board->is_ir104 ? (void *)4 :
+ board->is_acl7225b ? (void *)2 :
+ board->is_pcl725 ? (void *)1 : (void *)0;
+ }
+
+ if (board->has_ttl_io) {
+ /* TTL Digital Outputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_ttl_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_do_insn_bits;
+ s->private = (void *)2;
+
+ /* TTL Digital Inputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = board->n_ttl_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_di_insn_bits;
+ s->private = (void *)2;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver pcl730_driver = {
+ .driver_name = "pcl730",
+ .module = THIS_MODULE,
+ .attach = pcl730_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pcl730_boards[0].name,
+ .num_names = ARRAY_SIZE(pcl730_boards),
+ .offset = sizeof(struct pcl730_board),
+};
+module_comedi_driver(pcl730_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl812.c b/drivers/staging/comedi/drivers/pcl812.c
new file mode 100644
index 000000000..03a3fd6cd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl812.c
@@ -0,0 +1,1317 @@
+/*
+ * comedi/drivers/pcl812.c
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ * hardware driver for Advantech cards
+ * card: PCL-812, PCL-812PG, PCL-813, PCL-813B
+ * driver: pcl812, pcl812pg, pcl813, pcl813b
+ * and for ADlink cards
+ * card: ACL-8112DG, ACL-8112HG, ACL-8112PG, ACL-8113, ACL-8216
+ * driver: acl8112dg, acl8112hg, acl8112pg, acl8113, acl8216
+ * and for ICP DAS cards
+ * card: ISO-813, A-821PGH, A-821PGL, A-821PGL-NDA, A-822PGH, A-822PGL,
+ * driver: iso813, a821pgh, a-821pgl, a-821pglnda, a822pgh, a822pgl,
+ * card: A-823PGH, A-823PGL, A-826PG
+ * driver: a823pgh, a823pgl, a826pg
+ */
+
+/*
+ * Driver: pcl812
+ * Description: Advantech PCL-812/PG, PCL-813/B,
+ * ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216,
+ * ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG,
+ * ICP DAS ISO-813
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ * Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg),
+ * PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg),
+ * ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216),
+ * [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl),
+ * A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl),
+ * A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg)
+ * Updated: Mon, 06 Aug 2007 12:03:15 +0100
+ * Status: works (I hope. My board fire up under my hands
+ * and I cann't test all features.)
+ *
+ * This driver supports insn and cmd interfaces. Some boards support only insn
+ * because their hardware don't allow more (PCL-813/B, ACL-8113, ISO-813).
+ * Data transfer over DMA is supported only when you measure only one
+ * channel, this is too hardware limitation of these boards.
+ *
+ * Options for PCL-812:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0=trigger source is internal 8253 with 2MHz clock
+ * 1=trigger source is external
+ * [4] - 0=A/D input range is +/-10V
+ * 1=A/D input range is +/-5V
+ * 2=A/D input range is +/-2.5V
+ * 3=A/D input range is +/-1.25V
+ * 4=A/D input range is +/-0.625V
+ * 5=A/D input range is +/-0.3125V
+ * [5] - 0=D/A outputs 0-5V (internal reference -5V)
+ * 1=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ *
+ * Options for PCL-812PG, ACL-8112PG:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0=trigger source is internal 8253 with 2MHz clock
+ * 1=trigger source is external
+ * [4] - 0=A/D have max +/-5V input
+ * 1=A/D have max +/-10V input
+ * [5] - 0=D/A outputs 0-5V (internal reference -5V)
+ * 1=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ *
+ * Options for ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH, ACL-8216, A-826PG:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0=trigger source is internal 8253 with 2MHz clock
+ * 1=trigger source is external
+ * [4] - 0=A/D channels are S.E.
+ * 1=A/D channels are DIFF
+ * [5] - 0=D/A outputs 0-5V (internal reference -5V)
+ * 1=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ *
+ * Options for A-821PGL/PGH:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - 0=A/D channels are S.E.
+ * 1=A/D channels are DIFF
+ * [3] - 0=D/A output 0-5V (internal reference -5V)
+ * 1=D/A output 0-10V (internal reference -10V)
+ *
+ * Options for A-821PGL-NDA:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - 0=A/D channels are S.E.
+ * 1=A/D channels are DIFF
+ *
+ * Options for PCL-813:
+ * [0] - IO Base
+ *
+ * Options for PCL-813B:
+ * [0] - IO Base
+ * [1] - 0= bipolar inputs
+ * 1= unipolar inputs
+ *
+ * Options for ACL-8113, ISO-813:
+ * [0] - IO Base
+ * [1] - 0= 10V bipolar inputs
+ * 1= 10V unipolar inputs
+ * 2= 20V bipolar inputs
+ * 3= 20V unipolar inputs
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/gfp.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+/* hardware types of the cards */
+#define boardPCL812PG 0 /* and ACL-8112PG */
+#define boardPCL813B 1
+#define boardPCL812 2
+#define boardPCL813 3
+#define boardISO813 5
+#define boardACL8113 6
+#define boardACL8112 7 /* ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH */
+#define boardACL8216 8 /* and ICP DAS A-826PG */
+#define boardA821 9 /* PGH, PGL, PGL/NDA versions */
+
+/*
+ * Register I/O map
+ */
+#define PCL812_TIMER_BASE 0x00
+#define PCL812_AI_LSB_REG 0x04
+#define PCL812_AI_MSB_REG 0x05
+#define PCL812_AI_MSB_DRDY (1 << 4)
+#define PCL812_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define PCL812_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define PCL812_DI_LSB_REG 0x06
+#define PCL812_DI_MSB_REG 0x07
+#define PCL812_STATUS_REG 0x08
+#define PCL812_STATUS_DRDY (1 << 5)
+#define PCL812_RANGE_REG 0x09
+#define PCL812_MUX_REG 0x0a
+#define PCL812_MUX_CHAN(x) ((x) << 0)
+#define PCL812_MUX_CS0 (1 << 4)
+#define PCL812_MUX_CS1 (1 << 5)
+#define PCL812_CTRL_REG 0x0b
+#define PCL812_CTRL_DISABLE_TRIG (0 << 0)
+#define PCL812_CTRL_SOFT_TRIG (1 << 0)
+#define PCL812_CTRL_PACER_DMA_TRIG (2 << 0)
+#define PCL812_CTRL_PACER_EOC_TRIG (6 << 0)
+#define PCL812_SOFTTRIG_REG 0x0c
+#define PCL812_DO_LSB_REG 0x0d
+#define PCL812_DO_MSB_REG 0x0e
+
+#define MAX_CHANLIST_LEN 256 /* length of scan list */
+
+static const struct comedi_lrange range_pcl812pg_ai = {
+ 5, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(0.3125)
+ }
+};
+
+static const struct comedi_lrange range_pcl812pg2_ai = {
+ 5, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range812_bipolar1_25 = {
+ 1, {
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range812_bipolar0_625 = {
+ 1, {
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range812_bipolar0_3125 = {
+ 1, {
+ BIP_RANGE(0.3125)
+ }
+};
+
+static const struct comedi_lrange range_pcl813b_ai = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_pcl813b2_ai = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_iso813_1_ai = {
+ 5, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(0.3125)
+ }
+};
+
+static const struct comedi_lrange range_iso813_1_2_ai = {
+ 5, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ UNI_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_iso813_2_ai = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_iso813_2_2_ai = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_acl8113_1_ai = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_acl8113_1_2_ai = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_acl8113_2_ai = {
+ 3, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_acl8113_2_2_ai = {
+ 3, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5)
+ }
+};
+
+static const struct comedi_lrange range_acl8112dg_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ BIP_RANGE(10)
+ }
+};
+
+static const struct comedi_lrange range_acl8112hg_ai = {
+ 12, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.005),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01),
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_a821pgh_ai = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.005)
+ }
+};
+
+struct pcl812_board {
+ const char *name;
+ int board_type;
+ int n_aichan;
+ int n_aochan;
+ unsigned int ai_ns_min;
+ const struct comedi_lrange *rangelist_ai;
+ unsigned int IRQbits;
+ unsigned int has_dma:1;
+ unsigned int has_16bit_ai:1;
+ unsigned int has_mpc508_mux:1;
+ unsigned int has_dio:1;
+};
+
+static const struct pcl812_board boardtypes[] = {
+ {
+ .name = "pcl812",
+ .board_type = boardPCL812,
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .ai_ns_min = 33000,
+ .rangelist_ai = &range_bipolar10,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "pcl812pg",
+ .board_type = boardPCL812PG,
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .ai_ns_min = 33000,
+ .rangelist_ai = &range_pcl812pg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "acl8112pg",
+ .board_type = boardPCL812PG,
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl812pg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "acl8112dg",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112dg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_mpc508_mux = 1,
+ .has_dio = 1,
+ }, {
+ .name = "acl8112hg",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112hg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_mpc508_mux = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a821pgl",
+ .board_type = boardA821,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 1,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b_ai,
+ .IRQbits = 0x000c,
+ .has_dio = 1,
+ }, {
+ .name = "a821pglnda",
+ .board_type = boardA821,
+ .n_aichan = 16, /* 8 differential */
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b_ai,
+ .IRQbits = 0x000c,
+ }, {
+ .name = "a821pgh",
+ .board_type = boardA821,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 1,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_a821pgh_ai,
+ .IRQbits = 0x000c,
+ .has_dio = 1,
+ }, {
+ .name = "a822pgl",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112dg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a822pgh",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112hg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a823pgl",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 8000,
+ .rangelist_ai = &range_acl8112dg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a823pgh",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 8000,
+ .rangelist_ai = &range_acl8112hg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "pcl813",
+ .board_type = boardPCL813,
+ .n_aichan = 32,
+ .rangelist_ai = &range_pcl813b_ai,
+ }, {
+ .name = "pcl813b",
+ .board_type = boardPCL813B,
+ .n_aichan = 32,
+ .rangelist_ai = &range_pcl813b_ai,
+ }, {
+ .name = "acl8113",
+ .board_type = boardACL8113,
+ .n_aichan = 32,
+ .rangelist_ai = &range_acl8113_1_ai,
+ }, {
+ .name = "iso813",
+ .board_type = boardISO813,
+ .n_aichan = 32,
+ .rangelist_ai = &range_iso813_1_ai,
+ }, {
+ .name = "acl8216",
+ .board_type = boardACL8216,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b2_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_16bit_ai = 1,
+ .has_mpc508_mux = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a826pg",
+ .board_type = boardACL8216,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b2_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_16bit_ai = 1,
+ .has_dio = 1,
+ },
+};
+
+struct pcl812_private {
+ struct comedi_isadma *dma;
+ unsigned char range_correction; /* =1 we must add 1 to range number */
+ unsigned int last_ai_chanspec;
+ unsigned char mode_reg_int; /* there is stored INT number for some card */
+ unsigned int ai_poll_ptr; /* how many sampes transfer poll */
+ unsigned int max_812_ai_mode0_rangewait; /* setling time for gain */
+ unsigned int use_diff:1;
+ unsigned int use_mpc508:1;
+ unsigned int use_ext_trg:1;
+ unsigned int ai_dma:1;
+ unsigned int ai_eos:1;
+};
+
+static void pcl812_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int unread_samples)
+{
+ struct pcl812_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned int bytes;
+ unsigned int max_samples;
+ unsigned int nsamples;
+
+ comedi_isadma_disable(dma->chan);
+
+ /* if using EOS, adapt DMA buffer to one scan */
+ bytes = devpriv->ai_eos ? comedi_bytes_per_scan(s) : desc->maxsize;
+ max_samples = comedi_bytes_to_samples(s, bytes);
+
+ /*
+ * Determine dma size based on the buffer size plus the number of
+ * unread samples and the number of samples remaining in the command.
+ */
+ nsamples = comedi_nsamples_left(s, max_samples + unread_samples);
+ if (nsamples > unread_samples) {
+ nsamples -= unread_samples;
+ desc->size = comedi_samples_to_bytes(s, nsamples);
+ comedi_isadma_program(desc);
+ }
+}
+
+static void pcl812_ai_set_chan_range(struct comedi_device *dev,
+ unsigned int chanspec, char wait)
+{
+ struct pcl812_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int mux = 0;
+
+ if (chanspec == devpriv->last_ai_chanspec)
+ return;
+
+ devpriv->last_ai_chanspec = chanspec;
+
+ if (devpriv->use_mpc508) {
+ if (devpriv->use_diff) {
+ mux |= PCL812_MUX_CS0 | PCL812_MUX_CS1;
+ } else {
+ if (chan < 8)
+ mux |= PCL812_MUX_CS0;
+ else
+ mux |= PCL812_MUX_CS1;
+ }
+ }
+
+ outb(mux | PCL812_MUX_CHAN(chan), dev->iobase + PCL812_MUX_REG);
+ outb(range + devpriv->range_correction, dev->iobase + PCL812_RANGE_REG);
+
+ if (wait)
+ /*
+ * XXX this depends on selected range and can be very long for
+ * some high gain ranges!
+ */
+ udelay(devpriv->max_812_ai_mode0_rangewait);
+}
+
+static void pcl812_ai_clear_eoc(struct comedi_device *dev)
+{
+ /* writing any value clears the interrupt request */
+ outb(0, dev->iobase + PCL812_STATUS_REG);
+}
+
+static void pcl812_ai_soft_trig(struct comedi_device *dev)
+{
+ /* writing any value triggers a software conversion */
+ outb(255, dev->iobase + PCL812_SOFTTRIG_REG);
+}
+
+static unsigned int pcl812_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL812_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL812_AI_LSB_REG);
+
+ return val & s->maxdata;
+}
+
+static int pcl812_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ if (s->maxdata > 0x0fff) {
+ status = inb(dev->iobase + PCL812_STATUS_REG);
+ if ((status & PCL812_STATUS_DRDY) == 0)
+ return 0;
+ } else {
+ status = inb(dev->iobase + PCL812_AI_MSB_REG);
+ if ((status & PCL812_AI_MSB_DRDY) == 0)
+ return 0;
+ }
+ return -EBUSY;
+}
+
+static int pcl812_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct pcl812_board *board = dev->board_ptr;
+ struct pcl812_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int flags;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+
+ if (devpriv->use_ext_trg)
+ flags = TRIG_EXT;
+ else
+ flags = TRIG_TIMER;
+ err |= comedi_check_trigger_src(&cmd->convert_src, flags);
+
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_ns_min);
+ } else { /* TRIG_EXT */
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ }
+
+ err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ unsigned int arg = cmd->convert_arg;
+
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int ctrl = 0;
+ unsigned int i;
+
+ pcl812_ai_set_chan_range(dev, cmd->chanlist[0], 1);
+
+ if (dma) { /* check if we can use DMA transfer */
+ devpriv->ai_dma = 1;
+ for (i = 1; i < cmd->chanlist_len; i++)
+ if (cmd->chanlist[0] != cmd->chanlist[i]) {
+ /* we cann't use DMA :-( */
+ devpriv->ai_dma = 0;
+ break;
+ }
+ } else {
+ devpriv->ai_dma = 0;
+ }
+
+ devpriv->ai_poll_ptr = 0;
+
+ /* don't we want wake up every scan? */
+ if (cmd->flags & CMDF_WAKE_EOS) {
+ devpriv->ai_eos = 1;
+
+ /* DMA is useless for this situation */
+ if (cmd->chanlist_len == 1)
+ devpriv->ai_dma = 0;
+ }
+
+ if (devpriv->ai_dma) {
+ /* setup and enable dma for the first buffer */
+ dma->cur_dma = 0;
+ pcl812_ai_setup_dma(dev, s, 0);
+ }
+
+ switch (cmd->convert_src) {
+ case TRIG_TIMER:
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ break;
+ }
+
+ if (devpriv->ai_dma)
+ ctrl |= PCL812_CTRL_PACER_DMA_TRIG;
+ else
+ ctrl |= PCL812_CTRL_PACER_EOC_TRIG;
+ outb(devpriv->mode_reg_int | ctrl, dev->iobase + PCL812_CTRL_REG);
+
+ return 0;
+}
+
+static bool pcl812_ai_next_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg) {
+ s->async->events |= COMEDI_CB_EOA;
+ return false;
+ }
+
+ return true;
+}
+
+static void pcl812_handle_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int chan = s->async->cur_chan;
+ unsigned int next_chan;
+ unsigned short val;
+
+ if (pcl812_ai_eoc(dev, s, NULL, 0)) {
+ dev_dbg(dev->class_dev, "A/D cmd IRQ without DRDY!\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+
+ val = pcl812_ai_get_sample(dev, s);
+ comedi_buf_write_samples(s, &val, 1);
+
+ /* Set up next channel. Added by abbotti 2010-01-20, but untested. */
+ next_chan = s->async->cur_chan;
+ if (cmd->chanlist[chan] != cmd->chanlist[next_chan])
+ pcl812_ai_set_chan_range(dev, cmd->chanlist[next_chan], 0);
+
+ pcl812_ai_next_chan(dev, s);
+}
+
+static void transfer_from_dma_buf(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *ptr,
+ unsigned int bufptr, unsigned int len)
+{
+ unsigned int i;
+ unsigned short val;
+
+ for (i = len; i; i--) {
+ val = ptr[bufptr++];
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (!pcl812_ai_next_chan(dev, s))
+ break;
+ }
+}
+
+static void pcl812_handle_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned int nsamples;
+ int bufptr;
+
+ nsamples = comedi_bytes_to_samples(s, desc->size) -
+ devpriv->ai_poll_ptr;
+ bufptr = devpriv->ai_poll_ptr;
+ devpriv->ai_poll_ptr = 0;
+
+ /* restart dma with the next buffer */
+ dma->cur_dma = 1 - dma->cur_dma;
+ pcl812_ai_setup_dma(dev, s, nsamples);
+
+ transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples);
+}
+
+static irqreturn_t pcl812_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pcl812_private *devpriv = dev->private;
+
+ if (!dev->attached) {
+ pcl812_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->ai_dma)
+ pcl812_handle_dma(dev, s);
+ else
+ pcl812_handle_eoc(dev, s);
+
+ pcl812_ai_clear_eoc(dev);
+
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc;
+ unsigned long flags;
+ unsigned int poll;
+ int ret;
+
+ /* poll is valid only for DMA transfer */
+ if (!devpriv->ai_dma)
+ return 0;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ poll = comedi_isadma_poll(dma);
+ poll = comedi_bytes_to_samples(s, poll);
+ if (poll > devpriv->ai_poll_ptr) {
+ desc = &dma->desc[dma->cur_dma];
+ transfer_from_dma_buf(dev, s, desc->virt_addr,
+ devpriv->ai_poll_ptr,
+ poll - devpriv->ai_poll_ptr);
+ /* new buffer position */
+ devpriv->ai_poll_ptr = poll;
+
+ ret = comedi_buf_n_bytes_ready(s);
+ } else {
+ /* no new samples */
+ ret = 0;
+ }
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return ret;
+}
+
+static int pcl812_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+
+ if (devpriv->ai_dma)
+ comedi_isadma_disable(devpriv->dma->chan);
+
+ outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
+ pcl812_ai_clear_eoc(dev);
+ return 0;
+}
+
+static int pcl812_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl812_private *devpriv = dev->private;
+ int ret = 0;
+ int i;
+
+ outb(devpriv->mode_reg_int | PCL812_CTRL_SOFT_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+
+ pcl812_ai_set_chan_range(dev, insn->chanspec, 1);
+
+ for (i = 0; i < insn->n; i++) {
+ pcl812_ai_clear_eoc(dev);
+ pcl812_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, pcl812_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[i] = pcl812_ai_get_sample(dev, s);
+ }
+ outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+ pcl812_ai_clear_eoc(dev);
+
+ return ret ? ret : insn->n;
+}
+
+static int pcl812_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outb(val & 0xff, dev->iobase + PCL812_AO_LSB_REG(chan));
+ outb((val >> 8) & 0x0f, dev->iobase + PCL812_AO_MSB_REG(chan));
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pcl812_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PCL812_DI_LSB_REG) |
+ (inb(dev->iobase + PCL812_DI_MSB_REG) << 8);
+
+ return insn->n;
+}
+
+static int pcl812_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase + PCL812_DO_LSB_REG);
+ outb((s->state >> 8), dev->iobase + PCL812_DO_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pcl812_reset(struct comedi_device *dev)
+{
+ const struct pcl812_board *board = dev->board_ptr;
+ struct pcl812_private *devpriv = dev->private;
+ unsigned int chan;
+
+ /* disable analog input trigger */
+ outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+ pcl812_ai_clear_eoc(dev);
+
+ /*
+ * Invalidate last_ai_chanspec then set analog input to
+ * known channel/range.
+ */
+ devpriv->last_ai_chanspec = CR_PACK(16, 0, 0);
+ pcl812_ai_set_chan_range(dev, CR_PACK(0, 0, 0), 0);
+
+ /* set analog output channels to 0V */
+ for (chan = 0; chan < board->n_aochan; chan++) {
+ outb(0, dev->iobase + PCL812_AO_LSB_REG(chan));
+ outb(0, dev->iobase + PCL812_AO_MSB_REG(chan));
+ }
+
+ /* set all digital outputs low */
+ if (board->has_dio) {
+ outb(0, dev->iobase + PCL812_DO_MSB_REG);
+ outb(0, dev->iobase + PCL812_DO_LSB_REG);
+ }
+}
+
+static void pcl812_set_ai_range_table(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_devconfig *it)
+{
+ const struct pcl812_board *board = dev->board_ptr;
+ struct pcl812_private *devpriv = dev->private;
+
+ /* default to the range table from the boardinfo */
+ s->range_table = board->rangelist_ai;
+
+ /* now check the user config option based on the boardtype */
+ switch (board->board_type) {
+ case boardPCL812PG:
+ if (it->options[4] == 1)
+ s->range_table = &range_pcl812pg2_ai;
+ break;
+ case boardPCL812:
+ switch (it->options[4]) {
+ case 0:
+ s->range_table = &range_bipolar10;
+ break;
+ case 1:
+ s->range_table = &range_bipolar5;
+ break;
+ case 2:
+ s->range_table = &range_bipolar2_5;
+ break;
+ case 3:
+ s->range_table = &range812_bipolar1_25;
+ break;
+ case 4:
+ s->range_table = &range812_bipolar0_625;
+ break;
+ case 5:
+ s->range_table = &range812_bipolar0_3125;
+ break;
+ default:
+ s->range_table = &range_bipolar10;
+ break;
+ }
+ break;
+ case boardPCL813B:
+ if (it->options[1] == 1)
+ s->range_table = &range_pcl813b2_ai;
+ break;
+ case boardISO813:
+ switch (it->options[1]) {
+ case 0:
+ s->range_table = &range_iso813_1_ai;
+ break;
+ case 1:
+ s->range_table = &range_iso813_1_2_ai;
+ break;
+ case 2:
+ s->range_table = &range_iso813_2_ai;
+ devpriv->range_correction = 1;
+ break;
+ case 3:
+ s->range_table = &range_iso813_2_2_ai;
+ devpriv->range_correction = 1;
+ break;
+ default:
+ s->range_table = &range_iso813_1_ai;
+ break;
+ }
+ break;
+ case boardACL8113:
+ switch (it->options[1]) {
+ case 0:
+ s->range_table = &range_acl8113_1_ai;
+ break;
+ case 1:
+ s->range_table = &range_acl8113_1_2_ai;
+ break;
+ case 2:
+ s->range_table = &range_acl8113_2_ai;
+ devpriv->range_correction = 1;
+ break;
+ case 3:
+ s->range_table = &range_acl8113_2_2_ai;
+ devpriv->range_correction = 1;
+ break;
+ default:
+ s->range_table = &range_acl8113_1_ai;
+ break;
+ }
+ break;
+ }
+}
+
+static void pcl812_alloc_dma(struct comedi_device *dev, unsigned int dma_chan)
+{
+ struct pcl812_private *devpriv = dev->private;
+
+ /* only DMA channels 3 and 1 are valid */
+ if (!(dma_chan == 3 || dma_chan == 1))
+ return;
+
+ /* DMA uses two 8K buffers */
+ devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan,
+ PAGE_SIZE * 2, COMEDI_ISADMA_READ);
+}
+
+static void pcl812_free_dma(struct comedi_device *dev)
+{
+ struct pcl812_private *devpriv = dev->private;
+
+ if (devpriv)
+ comedi_isadma_free(devpriv->dma);
+}
+
+static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl812_board *board = dev->board_ptr;
+ struct pcl812_private *devpriv;
+ struct comedi_subdevice *s;
+ int n_subdevices;
+ int subdev;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ if (board->IRQbits) {
+ dev->pacer = comedi_8254_init(dev->iobase + PCL812_TIMER_BASE,
+ I8254_OSC_BASE_2MHZ,
+ I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ if ((1 << it->options[1]) & board->IRQbits) {
+ ret = request_irq(it->options[1], pcl812_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+ }
+
+ /* we need an IRQ to do DMA on channel 3 or 1 */
+ if (dev->irq && board->has_dma)
+ pcl812_alloc_dma(dev, it->options[2]);
+
+ /* differential analog inputs? */
+ switch (board->board_type) {
+ case boardA821:
+ if (it->options[2] == 1)
+ devpriv->use_diff = 1;
+ break;
+ case boardACL8112:
+ case boardACL8216:
+ if (it->options[4] == 1)
+ devpriv->use_diff = 1;
+ break;
+ }
+
+ n_subdevices = 1; /* all boardtypes have analog inputs */
+ if (board->n_aochan > 0)
+ n_subdevices++;
+ if (board->has_dio)
+ n_subdevices += 2;
+
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if (devpriv->use_diff) {
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = board->n_aichan / 2;
+ } else {
+ s->subdev_flags |= SDF_GROUND;
+ s->n_chan = board->n_aichan;
+ }
+ s->maxdata = board->has_16bit_ai ? 0xffff : 0x0fff;
+
+ pcl812_set_ai_range_table(dev, s, it);
+
+ s->insn_read = pcl812_ai_insn_read;
+
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = MAX_CHANLIST_LEN;
+ s->do_cmdtest = pcl812_ai_cmdtest;
+ s->do_cmd = pcl812_ai_cmd;
+ s->poll = pcl812_ai_poll;
+ s->cancel = pcl812_ai_cancel;
+ }
+
+ devpriv->use_mpc508 = board->has_mpc508_mux;
+
+ subdev++;
+
+ /* analog output */
+ if (board->n_aochan > 0) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = board->n_aochan;
+ s->maxdata = 0xfff;
+ s->range_table = &range_unipolar5;
+ switch (board->board_type) {
+ case boardA821:
+ if (it->options[3] == 1)
+ s->range_table = &range_unipolar10;
+ break;
+ case boardPCL812:
+ case boardACL8112:
+ case boardPCL812PG:
+ case boardACL8216:
+ if (it->options[5] == 1)
+ s->range_table = &range_unipolar10;
+ if (it->options[5] == 2)
+ s->range_table = &range_unknown;
+ break;
+ }
+ s->insn_write = pcl812_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ subdev++;
+ }
+
+ if (board->has_dio) {
+ /* Digital Input subdevice */
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl812_di_insn_bits;
+ subdev++;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl812_do_insn_bits;
+ subdev++;
+ }
+
+ switch (board->board_type) {
+ case boardACL8216:
+ case boardPCL812PG:
+ case boardPCL812:
+ case boardACL8112:
+ devpriv->max_812_ai_mode0_rangewait = 1;
+ if (it->options[3] > 0)
+ /* we use external trigger */
+ devpriv->use_ext_trg = 1;
+ break;
+ case boardA821:
+ devpriv->max_812_ai_mode0_rangewait = 1;
+ devpriv->mode_reg_int = (dev->irq << 4) & 0xf0;
+ break;
+ case boardPCL813B:
+ case boardPCL813:
+ case boardISO813:
+ case boardACL8113:
+ /* maybe there must by greatest timeout */
+ devpriv->max_812_ai_mode0_rangewait = 5;
+ break;
+ }
+
+ pcl812_reset(dev);
+
+ return 0;
+}
+
+static void pcl812_detach(struct comedi_device *dev)
+{
+ pcl812_free_dma(dev);
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcl812_driver = {
+ .driver_name = "pcl812",
+ .module = THIS_MODULE,
+ .attach = pcl812_attach,
+ .detach = pcl812_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl812_board),
+};
+module_comedi_driver(pcl812_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl816.c b/drivers/staging/comedi/drivers/pcl816.c
new file mode 100644
index 000000000..1ccb2f19f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl816.c
@@ -0,0 +1,712 @@
+/*
+ comedi/drivers/pcl816.c
+
+ Author: Juan Grigera <juan@grigera.com.ar>
+ based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812
+
+ hardware driver for Advantech cards:
+ card: PCL-816, PCL814B
+ driver: pcl816
+*/
+/*
+Driver: pcl816
+Description: Advantech PCL-816 cards, PCL-814
+Author: Juan Grigera <juan@grigera.com.ar>
+Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b)
+Status: works
+Updated: Tue, 2 Apr 2002 23:15:21 -0800
+
+PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO.
+Differences are at resolution (16 vs 12 bits).
+
+The driver support AI command mode, other subdevices not written.
+
+Analog output and digital input and output are not supported.
+
+Configuration Options:
+ [0] - IO Base
+ [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ [2] - DMA (0=disable, 1, 3)
+ [3] - 0, 10=10MHz clock for 8254
+ 1= 1MHz clock for 8254
+
+*/
+
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+/*
+ * Register I/O map
+ */
+#define PCL816_DO_DI_LSB_REG 0x00
+#define PCL816_DO_DI_MSB_REG 0x01
+#define PCL816_TIMER_BASE 0x04
+#define PCL816_AI_LSB_REG 0x08
+#define PCL816_AI_MSB_REG 0x09
+#define PCL816_RANGE_REG 0x09
+#define PCL816_CLRINT_REG 0x0a
+#define PCL816_MUX_REG 0x0b
+#define PCL816_MUX_SCAN(_first, _last) (((_last) << 4) | (_first))
+#define PCL816_CTRL_REG 0x0c
+#define PCL816_CTRL_DISABLE_TRIG (0 << 0)
+#define PCL816_CTRL_SOFT_TRIG (1 << 0)
+#define PCL816_CTRL_PACER_TRIG (1 << 1)
+#define PCL816_CTRL_EXT_TRIG (1 << 2)
+#define PCL816_CTRL_POE (1 << 3)
+#define PCL816_CTRL_DMAEN (1 << 4)
+#define PCL816_CTRL_INTEN (1 << 5)
+#define PCL816_CTRL_DMASRC_SLOT0 (0 << 6)
+#define PCL816_CTRL_DMASRC_SLOT1 (1 << 6)
+#define PCL816_CTRL_DMASRC_SLOT2 (2 << 6)
+#define PCL816_STATUS_REG 0x0d
+#define PCL816_STATUS_NEXT_CHAN_MASK (0xf << 0)
+#define PCL816_STATUS_INTSRC_MASK (3 << 4)
+#define PCL816_STATUS_INTSRC_SLOT0 (0 << 4)
+#define PCL816_STATUS_INTSRC_SLOT1 (1 << 4)
+#define PCL816_STATUS_INTSRC_SLOT2 (2 << 4)
+#define PCL816_STATUS_INTSRC_DMA (3 << 4)
+#define PCL816_STATUS_INTACT (1 << 6)
+#define PCL816_STATUS_DRDY (1 << 7)
+
+#define MAGIC_DMA_WORD 0x5a5a
+
+static const struct comedi_lrange range_pcl816 = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+struct pcl816_board {
+ const char *name;
+ int ai_maxdata;
+ int ao_maxdata;
+ int ai_chanlist;
+};
+
+static const struct pcl816_board boardtypes[] = {
+ {
+ .name = "pcl816",
+ .ai_maxdata = 0xffff,
+ .ao_maxdata = 0xffff,
+ .ai_chanlist = 1024,
+ }, {
+ .name = "pcl814b",
+ .ai_maxdata = 0x3fff,
+ .ao_maxdata = 0x3fff,
+ .ai_chanlist = 1024,
+ },
+};
+
+struct pcl816_private {
+ struct comedi_isadma *dma;
+ unsigned int ai_poll_ptr; /* how many sampes transfer poll */
+ unsigned int ai_cmd_running:1;
+ unsigned int ai_cmd_canceled:1;
+};
+
+static void pcl816_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int unread_samples)
+{
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize);
+ unsigned int nsamples;
+
+ comedi_isadma_disable(dma->chan);
+
+ /*
+ * Determine dma size based on the buffer maxsize plus the number of
+ * unread samples and the number of samples remaining in the command.
+ */
+ nsamples = comedi_nsamples_left(s, max_samples + unread_samples);
+ if (nsamples > unread_samples) {
+ nsamples -= unread_samples;
+ desc->size = comedi_samples_to_bytes(s, nsamples);
+ comedi_isadma_program(desc);
+ }
+}
+
+static void pcl816_ai_set_chan_range(struct comedi_device *dev,
+ unsigned int chan,
+ unsigned int range)
+{
+ outb(chan, dev->iobase + PCL816_MUX_REG);
+ outb(range, dev->iobase + PCL816_RANGE_REG);
+}
+
+static void pcl816_ai_set_chan_scan(struct comedi_device *dev,
+ unsigned int first_chan,
+ unsigned int last_chan)
+{
+ outb(PCL816_MUX_SCAN(first_chan, last_chan),
+ dev->iobase + PCL816_MUX_REG);
+}
+
+static void pcl816_ai_setup_chanlist(struct comedi_device *dev,
+ unsigned int *chanlist,
+ unsigned int seglen)
+{
+ unsigned int first_chan = CR_CHAN(chanlist[0]);
+ unsigned int last_chan;
+ unsigned int range;
+ unsigned int i;
+
+ /* store range list to card */
+ for (i = 0; i < seglen; i++) {
+ last_chan = CR_CHAN(chanlist[i]);
+ range = CR_RANGE(chanlist[i]);
+
+ pcl816_ai_set_chan_range(dev, last_chan, range);
+ }
+
+ udelay(1);
+
+ pcl816_ai_set_chan_scan(dev, first_chan, last_chan);
+}
+
+static void pcl816_ai_clear_eoc(struct comedi_device *dev)
+{
+ /* writing any value clears the interrupt request */
+ outb(0, dev->iobase + PCL816_CLRINT_REG);
+}
+
+static void pcl816_ai_soft_trig(struct comedi_device *dev)
+{
+ /* writing any value triggers a software conversion */
+ outb(0, dev->iobase + PCL816_AI_LSB_REG);
+}
+
+static unsigned int pcl816_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL816_AI_LSB_REG);
+
+ return val & s->maxdata;
+}
+
+static int pcl816_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCL816_STATUS_REG);
+ if ((status & PCL816_STATUS_DRDY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static bool pcl816_ai_next_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg) {
+ s->async->events |= COMEDI_CB_EOA;
+ return false;
+ }
+
+ return true;
+}
+
+static void transfer_from_dma_buf(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *ptr,
+ unsigned int bufptr, unsigned int len)
+{
+ unsigned short val;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ val = ptr[bufptr++];
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (!pcl816_ai_next_chan(dev, s))
+ return;
+ }
+}
+
+static irqreturn_t pcl816_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned int nsamples;
+ unsigned int bufptr;
+
+ if (!dev->attached || !devpriv->ai_cmd_running) {
+ pcl816_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->ai_cmd_canceled) {
+ devpriv->ai_cmd_canceled = 0;
+ pcl816_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ nsamples = comedi_bytes_to_samples(s, desc->size) -
+ devpriv->ai_poll_ptr;
+ bufptr = devpriv->ai_poll_ptr;
+ devpriv->ai_poll_ptr = 0;
+
+ /* restart dma with the next buffer */
+ dma->cur_dma = 1 - dma->cur_dma;
+ pcl816_ai_setup_dma(dev, s, nsamples);
+
+ transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples);
+
+ pcl816_ai_clear_eoc(dev);
+
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int check_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist,
+ unsigned int chanlen)
+{
+ unsigned int chansegment[16];
+ unsigned int i, nowmustbechan, seglen, segpos;
+
+ /* correct channel and range number check itself comedi/range.c */
+ if (chanlen < 1) {
+ dev_err(dev->class_dev, "range/channel list is empty!\n");
+ return 0;
+ }
+
+ if (chanlen > 1) {
+ /* first channel is every time ok */
+ chansegment[0] = chanlist[0];
+ for (i = 1, seglen = 1; i < chanlen; i++, seglen++) {
+ /* we detect loop, this must by finish */
+ if (chanlist[0] == chanlist[i])
+ break;
+ nowmustbechan =
+ (CR_CHAN(chansegment[i - 1]) + 1) % chanlen;
+ if (nowmustbechan != CR_CHAN(chanlist[i])) {
+ /* channel list isn't continuous :-( */
+ dev_dbg(dev->class_dev,
+ "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
+ i, CR_CHAN(chanlist[i]), nowmustbechan,
+ CR_CHAN(chanlist[0]));
+ return 0;
+ }
+ /* well, this is next correct channel in list */
+ chansegment[i] = chanlist[i];
+ }
+
+ /* check whole chanlist */
+ for (i = 0, segpos = 0; i < chanlen; i++) {
+ if (chanlist[i] != chansegment[i % seglen]) {
+ dev_dbg(dev->class_dev,
+ "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
+ i, CR_CHAN(chansegment[i]),
+ CR_RANGE(chansegment[i]),
+ CR_AREF(chansegment[i]),
+ CR_CHAN(chanlist[i % seglen]),
+ CR_RANGE(chanlist[i % seglen]),
+ CR_AREF(chansegment[i % seglen]));
+ return 0; /* chan/gain list is strange */
+ }
+ }
+ } else {
+ seglen = 1;
+ }
+
+ return seglen; /* we can serve this with MUX logic */
+}
+
+static int pcl816_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_EXT | TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
+ else /* TRIG_EXT */
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+ if (cmd->convert_src == TRIG_TIMER) {
+ unsigned int arg = cmd->convert_arg;
+
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* step 5: complain about special chanlist considerations */
+
+ if (cmd->chanlist) {
+ if (!check_channel_list(dev, s, cmd->chanlist,
+ cmd->chanlist_len))
+ return 5; /* incorrect channels list */
+ }
+
+ return 0;
+}
+
+static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int ctrl;
+ unsigned int seglen;
+
+ if (devpriv->ai_cmd_running)
+ return -EBUSY;
+
+ seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
+ if (seglen < 1)
+ return -EINVAL;
+ pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen);
+ udelay(1);
+
+ devpriv->ai_cmd_running = 1;
+ devpriv->ai_poll_ptr = 0;
+ devpriv->ai_cmd_canceled = 0;
+
+ /* setup and enable dma for the first buffer */
+ dma->cur_dma = 0;
+ pcl816_ai_setup_dma(dev, s, 0);
+
+ comedi_8254_set_mode(dev->pacer, 0, I8254_MODE1 | I8254_BINARY);
+ comedi_8254_write(dev->pacer, 0, 0x0ff);
+ udelay(1);
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+
+ ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN | PCL816_CTRL_DMASRC_SLOT0;
+ if (cmd->convert_src == TRIG_TIMER)
+ ctrl |= PCL816_CTRL_PACER_TRIG;
+ else /* TRIG_EXT */
+ ctrl |= PCL816_CTRL_EXT_TRIG;
+
+ outb(ctrl, dev->iobase + PCL816_CTRL_REG);
+ outb((dma->chan << 4) | dev->irq,
+ dev->iobase + PCL816_STATUS_REG);
+
+ return 0;
+}
+
+static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc;
+ unsigned long flags;
+ unsigned int poll;
+ int ret;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ poll = comedi_isadma_poll(dma);
+ poll = comedi_bytes_to_samples(s, poll);
+ if (poll > devpriv->ai_poll_ptr) {
+ desc = &dma->desc[dma->cur_dma];
+ transfer_from_dma_buf(dev, s, desc->virt_addr,
+ devpriv->ai_poll_ptr,
+ poll - devpriv->ai_poll_ptr);
+ /* new buffer position */
+ devpriv->ai_poll_ptr = poll;
+
+ comedi_handle_events(dev, s);
+
+ ret = comedi_buf_n_bytes_ready(s);
+ } else {
+ /* no new samples */
+ ret = 0;
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return ret;
+}
+
+static int pcl816_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+
+ if (!devpriv->ai_cmd_running)
+ return 0;
+
+ outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
+ pcl816_ai_clear_eoc(dev);
+
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
+
+ devpriv->ai_cmd_running = 0;
+ devpriv->ai_cmd_canceled = 1;
+
+ return 0;
+}
+
+static int pcl816_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int ret = 0;
+ int i;
+
+ outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG);
+
+ pcl816_ai_set_chan_range(dev, chan, range);
+ pcl816_ai_set_chan_scan(dev, chan, chan);
+
+ for (i = 0; i < insn->n; i++) {
+ pcl816_ai_clear_eoc(dev);
+ pcl816_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[i] = pcl816_ai_get_sample(dev, s);
+ }
+ outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
+ pcl816_ai_clear_eoc(dev);
+
+ return ret ? ret : insn->n;
+}
+
+static int pcl816_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) |
+ (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8);
+
+ return insn->n;
+}
+
+static int pcl816_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG);
+ outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pcl816_reset(struct comedi_device *dev)
+{
+ outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
+ pcl816_ai_set_chan_range(dev, 0, 0);
+ pcl816_ai_clear_eoc(dev);
+
+ /* set all digital outputs low */
+ outb(0, dev->iobase + PCL816_DO_DI_LSB_REG);
+ outb(0, dev->iobase + PCL816_DO_DI_MSB_REG);
+}
+
+static void pcl816_alloc_irq_and_dma(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct pcl816_private *devpriv = dev->private;
+ unsigned int irq_num = it->options[1];
+ unsigned int dma_chan = it->options[2];
+
+ /* only IRQs 2-7 and DMA channels 3 and 1 are valid */
+ if (!(irq_num >= 2 && irq_num <= 7) ||
+ !(dma_chan == 3 || dma_chan == 1))
+ return;
+
+ if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev))
+ return;
+
+ /* DMA uses two 16K buffers */
+ devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan,
+ PAGE_SIZE * 4, COMEDI_ISADMA_READ);
+ if (!devpriv->dma)
+ free_irq(irq_num, dev);
+ else
+ dev->irq = irq_num;
+}
+
+static void pcl816_free_dma(struct comedi_device *dev)
+{
+ struct pcl816_private *devpriv = dev->private;
+
+ if (devpriv)
+ comedi_isadma_free(devpriv->dma);
+}
+
+static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl816_board *board = dev->board_ptr;
+ struct pcl816_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ /* an IRQ and DMA are required to support async commands */
+ pcl816_alloc_irq_and_dma(dev, it);
+
+ dev->pacer = comedi_8254_init(dev->iobase + PCL816_TIMER_BASE,
+ I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_CMD_READ | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = &range_pcl816;
+ s->insn_read = pcl816_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = board->ai_chanlist;
+ s->do_cmdtest = pcl816_ai_cmdtest;
+ s->do_cmd = pcl816_ai_cmd;
+ s->poll = pcl816_ai_poll;
+ s->cancel = pcl816_ai_cancel;
+ }
+
+ /* Analog OUtput subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_UNUSED;
+#if 0
+ subdevs[1] = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = 1;
+ s->maxdata = board->ao_maxdata;
+ s->range_table = &range_pcl816;
+#endif
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl816_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl816_do_insn_bits;
+
+ pcl816_reset(dev);
+
+ return 0;
+}
+
+static void pcl816_detach(struct comedi_device *dev)
+{
+ if (dev->private) {
+ pcl816_ai_cancel(dev, dev->read_subdev);
+ pcl816_reset(dev);
+ }
+ pcl816_free_dma(dev);
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcl816_driver = {
+ .driver_name = "pcl816",
+ .module = THIS_MODULE,
+ .attach = pcl816_attach,
+ .detach = pcl816_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl816_board),
+};
+module_comedi_driver(pcl816_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl818.c b/drivers/staging/comedi/drivers/pcl818.c
new file mode 100644
index 000000000..e1bdde977
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl818.c
@@ -0,0 +1,1146 @@
+/*
+ * comedi/drivers/pcl818.c
+ *
+ * Driver: pcl818
+ * Description: Advantech PCL-818 cards, PCL-718
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ * Devices: [Advantech] PCL-818L (pcl818l), PCL-818H (pcl818h),
+ * PCL-818HD (pcl818hd), PCL-818HG (pcl818hg), PCL-818 (pcl818),
+ * PCL-718 (pcl718)
+ * Status: works
+ *
+ * All cards have 16 SE/8 DIFF ADCs, one or two DACs, 16 DI and 16 DO.
+ * Differences are only at maximal sample speed, range list and FIFO
+ * support.
+ * The driver support AI mode 0, 1, 3 other subdevices (AO, DI, DO) support
+ * only mode 0. If DMA/FIFO/INT are disabled then AI support only mode 0.
+ * PCL-818HD and PCL-818HG support 1kword FIFO. Driver support this FIFO
+ * but this code is untested.
+ * A word or two about DMA. Driver support DMA operations at two ways:
+ * 1) DMA uses two buffers and after one is filled then is generated
+ * INT and DMA restart with second buffer. With this mode I'm unable run
+ * more that 80Ksamples/secs without data dropouts on K6/233.
+ * 2) DMA uses one buffer and run in autoinit mode and the data are
+ * from DMA buffer moved on the fly with 2kHz interrupts from RTC.
+ * This mode is used if the interrupt 8 is available for allocation.
+ * If not, then first DMA mode is used. With this I can run at
+ * full speed one card (100ksamples/secs) or two cards with
+ * 60ksamples/secs each (more is problem on account of ISA limitations).
+ * To use this mode you must have compiled kernel with disabled
+ * "Enhanced Real Time Clock Support".
+ * Maybe you can have problems if you use xntpd or similar.
+ * If you've data dropouts with DMA mode 2 then:
+ * a) disable IDE DMA
+ * b) switch text mode console to fb.
+ *
+ * Options for PCL-818L:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0, 10=10MHz clock for 8254
+ * 1= 1MHz clock for 8254
+ * [4] - 0, 5=A/D input -5V.. +5V
+ * 1, 10=A/D input -10V..+10V
+ * [5] - 0, 5=D/A output 0-5V (internal reference -5V)
+ * 1, 10=D/A output 0-10V (internal reference -10V)
+ * 2 =D/A output unknown (external reference)
+ *
+ * Options for PCL-818, PCL-818H:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0, 10=10MHz clock for 8254
+ * 1= 1MHz clock for 8254
+ * [4] - 0, 5=D/A output 0-5V (internal reference -5V)
+ * 1, 10=D/A output 0-10V (internal reference -10V)
+ * 2 =D/A output unknown (external reference)
+ *
+ * Options for PCL-818HD, PCL-818HG:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - DMA/FIFO (-1=use FIFO, 0=disable both FIFO and DMA,
+ * 1=use DMA ch 1, 3=use DMA ch 3)
+ * [3] - 0, 10=10MHz clock for 8254
+ * 1= 1MHz clock for 8254
+ * [4] - 0, 5=D/A output 0-5V (internal reference -5V)
+ * 1, 10=D/A output 0-10V (internal reference -10V)
+ * 2 =D/A output unknown (external reference)
+ *
+ * Options for PCL-718:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0, 10=10MHz clock for 8254
+ * 1= 1MHz clock for 8254
+ * [4] - 0=A/D Range is +/-10V
+ * 1= +/-5V
+ * 2= +/-2.5V
+ * 3= +/-1V
+ * 4= +/-0.5V
+ * 5= user defined bipolar
+ * 6= 0-10V
+ * 7= 0-5V
+ * 8= 0-2V
+ * 9= 0-1V
+ * 10= user defined unipolar
+ * [5] - 0, 5=D/A outputs 0-5V (internal reference -5V)
+ * 1, 10=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ * [6] - 0, 60=max 60kHz A/D sampling
+ * 1,100=max 100kHz A/D sampling (PCL-718 with Option 001 installed)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+/* boards constants */
+
+#define boardPCL818L 0
+#define boardPCL818H 1
+#define boardPCL818HD 2
+#define boardPCL818HG 3
+#define boardPCL818 4
+#define boardPCL718 5
+
+/*
+ * Register I/O map
+ */
+#define PCL818_AI_LSB_REG 0x00
+#define PCL818_AI_MSB_REG 0x01
+#define PCL818_RANGE_REG 0x01
+#define PCL818_MUX_REG 0x02
+#define PCL818_MUX_SCAN(_first, _last) (((_last) << 4) | (_first))
+#define PCL818_DO_DI_LSB_REG 0x03
+#define PCL818_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define PCL818_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define PCL818_STATUS_REG 0x08
+#define PCL818_STATUS_NEXT_CHAN_MASK (0xf << 0)
+#define PCL818_STATUS_INT (1 << 4)
+#define PCL818_STATUS_MUX (1 << 5)
+#define PCL818_STATUS_UNI (1 << 6)
+#define PCL818_STATUS_EOC (1 << 7)
+#define PCL818_CTRL_REG 0x09
+#define PCL818_CTRL_DISABLE_TRIG (0 << 0)
+#define PCL818_CTRL_SOFT_TRIG (1 << 0)
+#define PCL818_CTRL_EXT_TRIG (2 << 0)
+#define PCL818_CTRL_PACER_TRIG (3 << 0)
+#define PCL818_CTRL_DMAE (1 << 2)
+#define PCL818_CTRL_IRQ(x) ((x) << 4)
+#define PCL818_CTRL_INTE (1 << 7)
+#define PCL818_CNTENABLE_REG 0x0a
+#define PCL818_CNTENABLE_PACER_ENA (0 << 0)
+#define PCL818_CNTENABLE_PACER_TRIG0 (1 << 0)
+#define PCL818_CNTENABLE_CNT0_EXT_CLK (0 << 1)
+#define PCL818_CNTENABLE_CNT0_INT_CLK (1 << 1)
+#define PCL818_DO_DI_MSB_REG 0x0b
+#define PCL818_TIMER_BASE 0x0c
+
+/* W: fifo enable/disable */
+#define PCL818_FI_ENABLE 6
+/* W: fifo interrupt clear */
+#define PCL818_FI_INTCLR 20
+/* W: fifo interrupt clear */
+#define PCL818_FI_FLUSH 25
+/* R: fifo status */
+#define PCL818_FI_STATUS 25
+/* R: one record from FIFO */
+#define PCL818_FI_DATALO 23
+#define PCL818_FI_DATAHI 24
+
+#define MAGIC_DMA_WORD 0x5a5a
+
+static const struct comedi_lrange range_pcl818h_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ BIP_RANGE(10)
+ }
+};
+
+static const struct comedi_lrange range_pcl818hg_ai = {
+ 10, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.005),
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.01),
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_pcl818l_l_ai = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_pcl818l_h_ai = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range718_bipolar1 = {
+ 1, {
+ BIP_RANGE(1)
+ }
+};
+
+static const struct comedi_lrange range718_bipolar0_5 = {
+ 1, {
+ BIP_RANGE(0.5)
+ }
+};
+
+static const struct comedi_lrange range718_unipolar2 = {
+ 1, {
+ UNI_RANGE(2)
+ }
+};
+
+static const struct comedi_lrange range718_unipolar1 = {
+ 1, {
+ BIP_RANGE(1)
+ }
+};
+
+struct pcl818_board {
+ const char *name;
+ unsigned int ns_min;
+ int n_aochan;
+ const struct comedi_lrange *ai_range_type;
+ unsigned int has_dma:1;
+ unsigned int has_fifo:1;
+ unsigned int is_818:1;
+};
+
+static const struct pcl818_board boardtypes[] = {
+ {
+ .name = "pcl818l",
+ .ns_min = 25000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818l_l_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818h",
+ .ns_min = 10000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818hd",
+ .ns_min = 10000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .has_fifo = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818hg",
+ .ns_min = 10000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818hg_ai,
+ .has_dma = 1,
+ .has_fifo = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818",
+ .ns_min = 10000,
+ .n_aochan = 2,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl718",
+ .ns_min = 16000,
+ .n_aochan = 2,
+ .ai_range_type = &range_unipolar5,
+ .has_dma = 1,
+ }, {
+ .name = "pcm3718",
+ .ns_min = 10000,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ },
+};
+
+struct pcl818_private {
+ struct comedi_isadma *dma;
+ /* manimal allowed delay between samples (in us) for actual card */
+ unsigned int ns_min;
+ /* MUX setting for actual AI operations */
+ unsigned int act_chanlist[16];
+ unsigned int act_chanlist_len; /* how long is actual MUX list */
+ unsigned int act_chanlist_pos; /* actual position in MUX list */
+ unsigned int usefifo:1;
+ unsigned int ai_cmd_running:1;
+ unsigned int ai_cmd_canceled:1;
+};
+
+static void pcl818_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int unread_samples)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize);
+ unsigned int nsamples;
+
+ comedi_isadma_disable(dma->chan);
+
+ /*
+ * Determine dma size based on the buffer maxsize plus the number of
+ * unread samples and the number of samples remaining in the command.
+ */
+ nsamples = comedi_nsamples_left(s, max_samples + unread_samples);
+ if (nsamples > unread_samples) {
+ nsamples -= unread_samples;
+ desc->size = comedi_samples_to_bytes(s, nsamples);
+ comedi_isadma_program(desc);
+ }
+}
+
+static void pcl818_ai_set_chan_range(struct comedi_device *dev,
+ unsigned int chan,
+ unsigned int range)
+{
+ outb(chan, dev->iobase + PCL818_MUX_REG);
+ outb(range, dev->iobase + PCL818_RANGE_REG);
+}
+
+static void pcl818_ai_set_chan_scan(struct comedi_device *dev,
+ unsigned int first_chan,
+ unsigned int last_chan)
+{
+ outb(PCL818_MUX_SCAN(first_chan, last_chan),
+ dev->iobase + PCL818_MUX_REG);
+}
+
+static void pcl818_ai_setup_chanlist(struct comedi_device *dev,
+ unsigned int *chanlist,
+ unsigned int seglen)
+{
+ struct pcl818_private *devpriv = dev->private;
+ unsigned int first_chan = CR_CHAN(chanlist[0]);
+ unsigned int last_chan;
+ unsigned int range;
+ int i;
+
+ devpriv->act_chanlist_len = seglen;
+ devpriv->act_chanlist_pos = 0;
+
+ /* store range list to card */
+ for (i = 0; i < seglen; i++) {
+ last_chan = CR_CHAN(chanlist[i]);
+ range = CR_RANGE(chanlist[i]);
+
+ devpriv->act_chanlist[i] = last_chan;
+
+ pcl818_ai_set_chan_range(dev, last_chan, range);
+ }
+
+ udelay(1);
+
+ pcl818_ai_set_chan_scan(dev, first_chan, last_chan);
+}
+
+static void pcl818_ai_clear_eoc(struct comedi_device *dev)
+{
+ /* writing any value clears the interrupt request */
+ outb(0, dev->iobase + PCL818_STATUS_REG);
+}
+
+static void pcl818_ai_soft_trig(struct comedi_device *dev)
+{
+ /* writing any value triggers a software conversion */
+ outb(0, dev->iobase + PCL818_AI_LSB_REG);
+}
+
+static unsigned int pcl818_ai_get_fifo_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chan)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL818_FI_DATALO);
+ val |= (inb(dev->iobase + PCL818_FI_DATAHI) << 8);
+
+ if (chan)
+ *chan = val & 0xf;
+
+ return (val >> 4) & s->maxdata;
+}
+
+static unsigned int pcl818_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chan)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL818_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL818_AI_LSB_REG);
+
+ if (chan)
+ *chan = val & 0xf;
+
+ return (val >> 4) & s->maxdata;
+}
+
+static int pcl818_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCL818_STATUS_REG);
+ if (status & PCL818_STATUS_INT)
+ return 0;
+ return -EBUSY;
+}
+
+static bool pcl818_ai_write_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan, unsigned int val)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int expected_chan;
+
+ expected_chan = devpriv->act_chanlist[devpriv->act_chanlist_pos];
+ if (chan != expected_chan) {
+ dev_dbg(dev->class_dev,
+ "A/D mode1/3 %s - channel dropout %d!=%d !\n",
+ (devpriv->dma) ? "DMA" :
+ (devpriv->usefifo) ? "FIFO" : "IRQ",
+ chan, expected_chan);
+ s->async->events |= COMEDI_CB_ERROR;
+ return false;
+ }
+
+ comedi_buf_write_samples(s, &val, 1);
+
+ devpriv->act_chanlist_pos++;
+ if (devpriv->act_chanlist_pos >= devpriv->act_chanlist_len)
+ devpriv->act_chanlist_pos = 0;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg) {
+ s->async->events |= COMEDI_CB_EOA;
+ return false;
+ }
+
+ return true;
+}
+
+static void pcl818_handle_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int chan;
+ unsigned int val;
+
+ if (pcl818_ai_eoc(dev, s, NULL, 0)) {
+ dev_err(dev->class_dev, "A/D mode1/3 IRQ without DRDY!\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+
+ val = pcl818_ai_get_sample(dev, s, &chan);
+ pcl818_ai_write_sample(dev, s, chan, val);
+}
+
+static void pcl818_handle_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+ unsigned short *ptr = desc->virt_addr;
+ unsigned int nsamples = comedi_bytes_to_samples(s, desc->size);
+ unsigned int chan;
+ unsigned int val;
+ int i;
+
+ /* restart dma with the next buffer */
+ dma->cur_dma = 1 - dma->cur_dma;
+ pcl818_ai_setup_dma(dev, s, nsamples);
+
+ for (i = 0; i < nsamples; i++) {
+ val = ptr[i];
+ chan = val & 0xf;
+ val = (val >> 4) & s->maxdata;
+ if (!pcl818_ai_write_sample(dev, s, chan, val))
+ break;
+ }
+}
+
+static void pcl818_handle_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int status;
+ unsigned int chan;
+ unsigned int val;
+ int i, len;
+
+ status = inb(dev->iobase + PCL818_FI_STATUS);
+
+ if (status & 4) {
+ dev_err(dev->class_dev, "A/D mode1/3 FIFO overflow!\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+
+ if (status & 1) {
+ dev_err(dev->class_dev,
+ "A/D mode1/3 FIFO interrupt without data!\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ return;
+ }
+
+ if (status & 2)
+ len = 512;
+ else
+ len = 0;
+
+ for (i = 0; i < len; i++) {
+ val = pcl818_ai_get_fifo_sample(dev, s, &chan);
+ if (!pcl818_ai_write_sample(dev, s, chan, val))
+ break;
+ }
+}
+
+static irqreturn_t pcl818_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (!dev->attached || !devpriv->ai_cmd_running) {
+ pcl818_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->ai_cmd_canceled) {
+ /*
+ * The cleanup from ai_cancel() has been delayed
+ * until now because the card doesn't seem to like
+ * being reprogrammed while a DMA transfer is in
+ * progress.
+ */
+ s->async->scans_done = cmd->stop_arg;
+ s->cancel(dev, s);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->dma)
+ pcl818_handle_dma(dev, s);
+ else if (devpriv->usefifo)
+ pcl818_handle_fifo(dev, s);
+ else
+ pcl818_handle_eoc(dev, s);
+
+ pcl818_ai_clear_eoc(dev);
+
+ comedi_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int check_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist, unsigned int n_chan)
+{
+ unsigned int chansegment[16];
+ unsigned int i, nowmustbechan, seglen, segpos;
+
+ /* correct channel and range number check itself comedi/range.c */
+ if (n_chan < 1) {
+ dev_err(dev->class_dev, "range/channel list is empty!\n");
+ return 0;
+ }
+
+ if (n_chan > 1) {
+ /* first channel is every time ok */
+ chansegment[0] = chanlist[0];
+ /* build part of chanlist */
+ for (i = 1, seglen = 1; i < n_chan; i++, seglen++) {
+ /* we detect loop, this must by finish */
+
+ if (chanlist[0] == chanlist[i])
+ break;
+ nowmustbechan =
+ (CR_CHAN(chansegment[i - 1]) + 1) % s->n_chan;
+ if (nowmustbechan != CR_CHAN(chanlist[i])) {
+ /* channel list isn't continuous :-( */
+ dev_dbg(dev->class_dev,
+ "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
+ i, CR_CHAN(chanlist[i]), nowmustbechan,
+ CR_CHAN(chanlist[0]));
+ return 0;
+ }
+ /* well, this is next correct channel in list */
+ chansegment[i] = chanlist[i];
+ }
+
+ /* check whole chanlist */
+ for (i = 0, segpos = 0; i < n_chan; i++) {
+ if (chanlist[i] != chansegment[i % seglen]) {
+ dev_dbg(dev->class_dev,
+ "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
+ i, CR_CHAN(chansegment[i]),
+ CR_RANGE(chansegment[i]),
+ CR_AREF(chansegment[i]),
+ CR_CHAN(chanlist[i % seglen]),
+ CR_RANGE(chanlist[i % seglen]),
+ CR_AREF(chansegment[i % seglen]));
+ return 0; /* chan/gain list is strange */
+ }
+ }
+ } else {
+ seglen = 1;
+ }
+ return seglen;
+}
+
+static int check_single_ended(unsigned int port)
+{
+ if (inb(port + PCL818_STATUS_REG) & PCL818_STATUS_MUX)
+ return 1;
+ return 0;
+}
+
+static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcl818_board *board = dev->board_ptr;
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ board->ns_min);
+ } else { /* TRIG_EXT */
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ unsigned int arg = cmd->convert_arg;
+
+ comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* step 5: complain about special chanlist considerations */
+
+ if (cmd->chanlist) {
+ if (!check_channel_list(dev, s, cmd->chanlist,
+ cmd->chanlist_len))
+ return 5; /* incorrect channels list */
+ }
+
+ return 0;
+}
+
+static int pcl818_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int ctrl = 0;
+ unsigned int seglen;
+
+ if (devpriv->ai_cmd_running)
+ return -EBUSY;
+
+ seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
+ if (seglen < 1)
+ return -EINVAL;
+ pcl818_ai_setup_chanlist(dev, cmd->chanlist, seglen);
+
+ devpriv->ai_cmd_running = 1;
+ devpriv->ai_cmd_canceled = 0;
+ devpriv->act_chanlist_pos = 0;
+
+ if (cmd->convert_src == TRIG_TIMER)
+ ctrl |= PCL818_CTRL_PACER_TRIG;
+ else
+ ctrl |= PCL818_CTRL_EXT_TRIG;
+
+ outb(PCL818_CNTENABLE_PACER_ENA, dev->iobase + PCL818_CNTENABLE_REG);
+
+ if (dma) {
+ /* setup and enable dma for the first buffer */
+ dma->cur_dma = 0;
+ pcl818_ai_setup_dma(dev, s, 0);
+
+ ctrl |= PCL818_CTRL_INTE | PCL818_CTRL_IRQ(dev->irq) |
+ PCL818_CTRL_DMAE;
+ } else if (devpriv->usefifo) {
+ /* enable FIFO */
+ outb(1, dev->iobase + PCL818_FI_ENABLE);
+ } else {
+ ctrl |= PCL818_CTRL_INTE | PCL818_CTRL_IRQ(dev->irq);
+ }
+ outb(ctrl, dev->iobase + PCL818_CTRL_REG);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ comedi_8254_update_divisors(dev->pacer);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+ }
+
+ return 0;
+}
+
+static int pcl818_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_isadma *dma = devpriv->dma;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (!devpriv->ai_cmd_running)
+ return 0;
+
+ if (dma) {
+ if (cmd->stop_src == TRIG_NONE ||
+ (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done < cmd->stop_arg)) {
+ if (!devpriv->ai_cmd_canceled) {
+ /*
+ * Wait for running dma transfer to end,
+ * do cleanup in interrupt.
+ */
+ devpriv->ai_cmd_canceled = 1;
+ return 0;
+ }
+ }
+ comedi_isadma_disable(dma->chan);
+ }
+
+ outb(PCL818_CTRL_DISABLE_TRIG, dev->iobase + PCL818_CTRL_REG);
+ comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
+ pcl818_ai_clear_eoc(dev);
+
+ if (devpriv->usefifo) { /* FIFO shutdown */
+ outb(0, dev->iobase + PCL818_FI_INTCLR);
+ outb(0, dev->iobase + PCL818_FI_FLUSH);
+ outb(0, dev->iobase + PCL818_FI_ENABLE);
+ }
+ devpriv->ai_cmd_running = 0;
+ devpriv->ai_cmd_canceled = 0;
+
+ return 0;
+}
+
+static int pcl818_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int ret = 0;
+ int i;
+
+ outb(PCL818_CTRL_SOFT_TRIG, dev->iobase + PCL818_CTRL_REG);
+
+ pcl818_ai_set_chan_range(dev, chan, range);
+ pcl818_ai_set_chan_scan(dev, chan, chan);
+
+ for (i = 0; i < insn->n; i++) {
+ pcl818_ai_clear_eoc(dev);
+ pcl818_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, pcl818_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[i] = pcl818_ai_get_sample(dev, s, NULL);
+ }
+ pcl818_ai_clear_eoc(dev);
+
+ return ret ? ret : insn->n;
+}
+
+static int pcl818_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outb((val & 0x000f) << 4,
+ dev->iobase + PCL818_AO_LSB_REG(chan));
+ outb((val & 0x0ff0) >> 4,
+ dev->iobase + PCL818_AO_MSB_REG(chan));
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pcl818_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PCL818_DO_DI_LSB_REG) |
+ (inb(dev->iobase + PCL818_DO_DI_MSB_REG) << 8);
+
+ return insn->n;
+}
+
+static int pcl818_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase + PCL818_DO_DI_LSB_REG);
+ outb((s->state >> 8), dev->iobase + PCL818_DO_DI_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pcl818_reset(struct comedi_device *dev)
+{
+ const struct pcl818_board *board = dev->board_ptr;
+ unsigned int chan;
+
+ /* flush and disable the FIFO */
+ if (board->has_fifo) {
+ outb(0, dev->iobase + PCL818_FI_INTCLR);
+ outb(0, dev->iobase + PCL818_FI_FLUSH);
+ outb(0, dev->iobase + PCL818_FI_ENABLE);
+ }
+
+ /* disable analog input trigger */
+ outb(PCL818_CTRL_DISABLE_TRIG, dev->iobase + PCL818_CTRL_REG);
+ pcl818_ai_clear_eoc(dev);
+
+ pcl818_ai_set_chan_range(dev, 0, 0);
+
+ /* stop pacer */
+ outb(PCL818_CNTENABLE_PACER_ENA, dev->iobase + PCL818_CNTENABLE_REG);
+
+ /* set analog output channels to 0V */
+ for (chan = 0; chan < board->n_aochan; chan++) {
+ outb(0, dev->iobase + PCL818_AO_LSB_REG(chan));
+ outb(0, dev->iobase + PCL818_AO_MSB_REG(chan));
+ }
+
+ /* set all digital outputs low */
+ outb(0, dev->iobase + PCL818_DO_DI_MSB_REG);
+ outb(0, dev->iobase + PCL818_DO_DI_LSB_REG);
+}
+
+static void pcl818_set_ai_range_table(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_devconfig *it)
+{
+ const struct pcl818_board *board = dev->board_ptr;
+
+ /* default to the range table from the boardinfo */
+ s->range_table = board->ai_range_type;
+
+ /* now check the user config option based on the boardtype */
+ if (board->is_818) {
+ if (it->options[4] == 1 || it->options[4] == 10) {
+ /* secondary range list jumper selectable */
+ s->range_table = &range_pcl818l_h_ai;
+ }
+ } else {
+ switch (it->options[4]) {
+ case 0:
+ s->range_table = &range_bipolar10;
+ break;
+ case 1:
+ s->range_table = &range_bipolar5;
+ break;
+ case 2:
+ s->range_table = &range_bipolar2_5;
+ break;
+ case 3:
+ s->range_table = &range718_bipolar1;
+ break;
+ case 4:
+ s->range_table = &range718_bipolar0_5;
+ break;
+ case 6:
+ s->range_table = &range_unipolar10;
+ break;
+ case 7:
+ s->range_table = &range_unipolar5;
+ break;
+ case 8:
+ s->range_table = &range718_unipolar2;
+ break;
+ case 9:
+ s->range_table = &range718_unipolar1;
+ break;
+ default:
+ s->range_table = &range_unknown;
+ break;
+ }
+ }
+}
+
+static void pcl818_alloc_dma(struct comedi_device *dev, unsigned int dma_chan)
+{
+ struct pcl818_private *devpriv = dev->private;
+
+ /* only DMA channels 3 and 1 are valid */
+ if (!(dma_chan == 3 || dma_chan == 1))
+ return;
+
+ /* DMA uses two 16K buffers */
+ devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan,
+ PAGE_SIZE * 4, COMEDI_ISADMA_READ);
+}
+
+static void pcl818_free_dma(struct comedi_device *dev)
+{
+ struct pcl818_private *devpriv = dev->private;
+
+ if (devpriv)
+ comedi_isadma_free(devpriv->dma);
+}
+
+static int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl818_board *board = dev->board_ptr;
+ struct pcl818_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int osc_base;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0],
+ board->has_fifo ? 0x20 : 0x10);
+ if (ret)
+ return ret;
+
+ /* we can use IRQ 2-7 for async command support */
+ if (it->options[1] >= 2 && it->options[1] <= 7) {
+ ret = request_irq(it->options[1], pcl818_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ /* should we use the FIFO? */
+ if (dev->irq && board->has_fifo && it->options[2] == -1)
+ devpriv->usefifo = 1;
+
+ /* we need an IRQ to do DMA on channel 3 or 1 */
+ if (dev->irq && board->has_dma)
+ pcl818_alloc_dma(dev, it->options[2]);
+
+ /* use 1MHz or 10MHz oscilator */
+ if ((it->options[3] == 0) || (it->options[3] == 10))
+ osc_base = I8254_OSC_BASE_10MHZ;
+ else
+ osc_base = I8254_OSC_BASE_1MHZ;
+
+ dev->pacer = comedi_8254_init(dev->iobase + PCL818_TIMER_BASE,
+ osc_base, I8254_IO8, 0);
+ if (!dev->pacer)
+ return -ENOMEM;
+
+ /* max sampling speed */
+ devpriv->ns_min = board->ns_min;
+ if (!board->is_818) {
+ /* extended PCL718 to 100kHz DAC */
+ if ((it->options[6] == 1) || (it->options[6] == 100))
+ devpriv->ns_min = 10000;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if (check_single_ended(dev->iobase)) {
+ s->n_chan = 16;
+ s->subdev_flags |= SDF_COMMON | SDF_GROUND;
+ } else {
+ s->n_chan = 8;
+ s->subdev_flags |= SDF_DIFF;
+ }
+ s->maxdata = 0x0fff;
+
+ pcl818_set_ai_range_table(dev, s, it);
+
+ s->insn_read = pcl818_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = ai_cmdtest;
+ s->do_cmd = pcl818_ai_cmd;
+ s->cancel = pcl818_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ if (board->n_aochan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = board->n_aochan;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_unipolar5;
+ if (board->is_818) {
+ if ((it->options[4] == 1) || (it->options[4] == 10))
+ s->range_table = &range_unipolar10;
+ if (it->options[4] == 2)
+ s->range_table = &range_unknown;
+ } else {
+ if ((it->options[5] == 1) || (it->options[5] == 10))
+ s->range_table = &range_unipolar10;
+ if (it->options[5] == 2)
+ s->range_table = &range_unknown;
+ }
+ s->insn_write = pcl818_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl818_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl818_do_insn_bits;
+
+ pcl818_reset(dev);
+
+ return 0;
+}
+
+static void pcl818_detach(struct comedi_device *dev)
+{
+ struct pcl818_private *devpriv = dev->private;
+
+ if (devpriv) {
+ pcl818_ai_cancel(dev, dev->read_subdev);
+ pcl818_reset(dev);
+ }
+ pcl818_free_dma(dev);
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcl818_driver = {
+ .driver_name = "pcl818",
+ .module = THIS_MODULE,
+ .attach = pcl818_attach,
+ .detach = pcl818_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl818_board),
+};
+module_comedi_driver(pcl818_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcm3724.c b/drivers/staging/comedi/drivers/pcm3724.c
new file mode 100644
index 000000000..6176dfa24
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcm3724.c
@@ -0,0 +1,220 @@
+/*
+ comedi/drivers/pcm724.c
+
+ Drew Csillag <drew_csillag@yahoo.com>
+
+ hardware driver for Advantech card:
+ card: PCM-3724
+ driver: pcm3724
+
+ Options for PCM-3724
+ [0] - IO Base
+*/
+/*
+Driver: pcm3724
+Description: Advantech PCM-3724
+Author: Drew Csillag <drew_csillag@yahoo.com>
+Devices: [Advantech] PCM-3724 (pcm724)
+Status: tested
+
+This is driver for digital I/O boards PCM-3724 with 48 DIO.
+It needs 8255.o for operations and only immediate mode is supported.
+See the source for configuration details.
+
+Copy/pasted/hacked from pcm724.c
+*/
+/*
+ * check_driver overrides:
+ * struct comedi_insn
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+
+#define BUF_C0 0x1
+#define BUF_B0 0x2
+#define BUF_A0 0x4
+#define BUF_C1 0x8
+#define BUF_B1 0x10
+#define BUF_A1 0x20
+
+#define GATE_A0 0x4
+#define GATE_B0 0x2
+#define GATE_C0 0x1
+#define GATE_A1 0x20
+#define GATE_B1 0x10
+#define GATE_C1 0x8
+
+/* used to track configured dios */
+struct priv_pcm3724 {
+ int dio_1;
+ int dio_2;
+};
+
+static int compute_buffer(int config, int devno, struct comedi_subdevice *s)
+{
+ /* 1 in io_bits indicates output */
+ if (s->io_bits & 0x0000ff) {
+ if (devno == 0)
+ config |= BUF_A0;
+ else
+ config |= BUF_A1;
+ }
+ if (s->io_bits & 0x00ff00) {
+ if (devno == 0)
+ config |= BUF_B0;
+ else
+ config |= BUF_B1;
+ }
+ if (s->io_bits & 0xff0000) {
+ if (devno == 0)
+ config |= BUF_C0;
+ else
+ config |= BUF_C1;
+ }
+ return config;
+}
+
+static void do_3724_config(struct comedi_device *dev,
+ struct comedi_subdevice *s, int chanspec)
+{
+ struct comedi_subdevice *s_dio1 = &dev->subdevices[0];
+ struct comedi_subdevice *s_dio2 = &dev->subdevices[1];
+ int config;
+ int buffer_config;
+ unsigned long port_8255_cfg;
+
+ config = I8255_CTRL_CW;
+ buffer_config = 0;
+
+ /* 1 in io_bits indicates output, 1 in config indicates input */
+ if (!(s->io_bits & 0x0000ff))
+ config |= I8255_CTRL_A_IO;
+
+ if (!(s->io_bits & 0x00ff00))
+ config |= I8255_CTRL_B_IO;
+
+ if (!(s->io_bits & 0xff0000))
+ config |= I8255_CTRL_C_HI_IO | I8255_CTRL_C_LO_IO;
+
+ buffer_config = compute_buffer(0, 0, s_dio1);
+ buffer_config = compute_buffer(buffer_config, 1, s_dio2);
+
+ if (s == s_dio1)
+ port_8255_cfg = dev->iobase + I8255_CTRL_REG;
+ else
+ port_8255_cfg = dev->iobase + I8255_SIZE + I8255_CTRL_REG;
+
+ outb(buffer_config, dev->iobase + 8); /* update buffer register */
+
+ outb(config, port_8255_cfg);
+}
+
+static void enable_chan(struct comedi_device *dev, struct comedi_subdevice *s,
+ int chanspec)
+{
+ struct priv_pcm3724 *priv = dev->private;
+ struct comedi_subdevice *s_dio1 = &dev->subdevices[0];
+ unsigned int mask;
+ int gatecfg;
+
+ gatecfg = 0;
+
+ mask = 1 << CR_CHAN(chanspec);
+ if (s == s_dio1)
+ priv->dio_1 |= mask;
+ else
+ priv->dio_2 |= mask;
+
+ if (priv->dio_1 & 0xff0000)
+ gatecfg |= GATE_C0;
+
+ if (priv->dio_1 & 0xff00)
+ gatecfg |= GATE_B0;
+
+ if (priv->dio_1 & 0xff)
+ gatecfg |= GATE_A0;
+
+ if (priv->dio_2 & 0xff0000)
+ gatecfg |= GATE_C1;
+
+ if (priv->dio_2 & 0xff00)
+ gatecfg |= GATE_B1;
+
+ if (priv->dio_2 & 0xff)
+ gatecfg |= GATE_A1;
+
+ outb(gatecfg, dev->iobase + 9);
+}
+
+/* overriding the 8255 insn config */
+static int subdev_3724_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x0000ff;
+ else if (chan < 16)
+ mask = 0x00ff00;
+ else if (chan < 20)
+ mask = 0x0f0000;
+ else
+ mask = 0xf00000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ do_3724_config(dev, s, insn->chanspec);
+ enable_chan(dev, s, insn->chanspec);
+
+ return insn->n;
+}
+
+static int pcm3724_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct priv_pcm3724 *priv;
+ struct comedi_subdevice *s;
+ int ret, i;
+
+ priv = comedi_alloc_devpriv(dev, sizeof(*priv));
+ if (!priv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE);
+ if (ret)
+ return ret;
+ s->insn_config = subdev_3724_insn_config;
+ }
+ return 0;
+}
+
+static struct comedi_driver pcm3724_driver = {
+ .driver_name = "pcm3724",
+ .module = THIS_MODULE,
+ .attach = pcm3724_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(pcm3724_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcmad.c b/drivers/staging/comedi/drivers/pcmad.c
new file mode 100644
index 000000000..12f94fe82
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmad.c
@@ -0,0 +1,158 @@
+/*
+ * pcmad.c
+ * Hardware driver for Winsystems PCM-A/D12 and PCM-A/D16
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000,2001 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: pcmad
+ * Description: Winsystems PCM-A/D12, PCM-A/D16
+ * Devices: [Winsystems] PCM-A/D12 (pcmad12), PCM-A/D16 (pcmad16)
+ * Author: ds
+ * Status: untested
+ *
+ * This driver was written on a bet that I couldn't write a driver
+ * in less than 2 hours. I won the bet, but never got paid. =(
+ *
+ * Configuration options:
+ * [0] - I/O port base
+ * [1] - IRQ (unused)
+ * [2] - Analog input reference (must match jumpers)
+ * 0 = single-ended (16 channels)
+ * 1 = differential (8 channels)
+ * [3] - Analog input encoding (must match jumpers)
+ * 0 = straight binary (0-5V input range)
+ * 1 = two's complement (+-10V input range)
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#define PCMAD_STATUS 0
+#define PCMAD_LSB 1
+#define PCMAD_MSB 2
+#define PCMAD_CONVERT 1
+
+struct pcmad_board_struct {
+ const char *name;
+ unsigned int ai_maxdata;
+};
+
+static const struct pcmad_board_struct pcmad_boards[] = {
+ {
+ .name = "pcmad12",
+ .ai_maxdata = 0x0fff,
+ }, {
+ .name = "pcmad16",
+ .ai_maxdata = 0xffff,
+ },
+};
+
+static int pcmad_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCMAD_STATUS);
+ if ((status & 0x3) == 0x3)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcmad_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ outb(chan, dev->iobase + PCMAD_CONVERT);
+
+ ret = comedi_timeout(dev, s, insn, pcmad_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(dev->iobase + PCMAD_LSB) |
+ (inb(dev->iobase + PCMAD_MSB) << 8);
+
+ /* data is shifted on the pcmad12, fix it */
+ if (s->maxdata == 0x0fff)
+ val >>= 4;
+
+ if (comedi_range_is_bipolar(s, range)) {
+ /* munge the two's complement value */
+ val ^= ((s->maxdata + 1) >> 1);
+ }
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int pcmad_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcmad_board_struct *board = dev->board_ptr;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x04);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ if (it->options[1]) {
+ /* 8 differential channels */
+ s->subdev_flags = SDF_READABLE | AREF_DIFF;
+ s->n_chan = 8;
+ } else {
+ /* 16 single-ended channels */
+ s->subdev_flags = SDF_READABLE | AREF_GROUND;
+ s->n_chan = 16;
+ }
+ s->len_chanlist = 1;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = it->options[2] ? &range_bipolar10 : &range_unipolar5;
+ s->insn_read = pcmad_ai_insn_read;
+
+ return 0;
+}
+
+static struct comedi_driver pcmad_driver = {
+ .driver_name = "pcmad",
+ .module = THIS_MODULE,
+ .attach = pcmad_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pcmad_boards[0].name,
+ .num_names = ARRAY_SIZE(pcmad_boards),
+ .offset = sizeof(pcmad_boards[0]),
+};
+module_comedi_driver(pcmad_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcmda12.c b/drivers/staging/comedi/drivers/pcmda12.c
new file mode 100644
index 000000000..d86c5e2cd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmda12.c
@@ -0,0 +1,174 @@
+/*
+ * pcmda12.c
+ * Driver for Winsystems PC-104 based PCM-D/A-12 8-channel AO board.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: pcmda12
+ * Description: A driver for the Winsystems PCM-D/A-12
+ * Devices: [Winsystems] PCM-D/A-12 (pcmda12)
+ * Author: Calin Culianu <calin@ajvar.org>
+ * Updated: Fri, 13 Jan 2006 12:01:01 -0500
+ * Status: works
+ *
+ * A driver for the relatively straightforward-to-program PCM-D/A-12.
+ * This board doesn't support commands, and the only way to set its
+ * analog output range is to jumper the board. As such,
+ * comedi_data_write() ignores the range value specified.
+ *
+ * The board uses 16 consecutive I/O addresses starting at the I/O port
+ * base address. Each address corresponds to the LSB then MSB of a
+ * particular channel from 0-7.
+ *
+ * Note that the board is not ISA-PNP capable and thus needs the I/O
+ * port comedi_config parameter.
+ *
+ * Note that passing a nonzero value as the second config option will
+ * enable "simultaneous xfer" mode for this board, in which AO writes
+ * will not take effect until a subsequent read of any AO channel. This
+ * is so that one can speed up programming by preloading all AO registers
+ * with values before simultaneously setting them to take effect with one
+ * read command.
+ *
+ * Configuration Options:
+ * [0] - I/O port base address
+ * [1] - Do Simultaneous Xfer (see description)
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/* AI range is not configurable, it's set by jumpers on the board */
+static const struct comedi_lrange pcmda12_ranges = {
+ 3, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5)
+ }
+};
+
+struct pcmda12_private {
+ int simultaneous_xfer_mode;
+};
+
+static int pcmda12_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcmda12_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ unsigned long ioreg = dev->iobase + (chan * 2);
+ int i;
+
+ for (i = 0; i < insn->n; ++i) {
+ val = data[i];
+ outb(val & 0xff, ioreg);
+ outb((val >> 8) & 0xff, ioreg + 1);
+
+ /*
+ * Initiate transfer if not in simultaneaous xfer
+ * mode by reading one of the AO registers.
+ */
+ if (!devpriv->simultaneous_xfer_mode)
+ inb(ioreg);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pcmda12_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcmda12_private *devpriv = dev->private;
+
+ /*
+ * Initiate simultaneaous xfer mode by reading one of the
+ * AO registers. All analog outputs will then be updated.
+ */
+ if (devpriv->simultaneous_xfer_mode)
+ inb(dev->iobase);
+
+ return comedi_readback_insn_read(dev, s, insn, data);
+}
+
+static void pcmda12_ao_reset(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ int i;
+
+ for (i = 0; i < s->n_chan; ++i) {
+ outb(0, dev->iobase + (i * 2));
+ outb(0, dev->iobase + (i * 2) + 1);
+ }
+ /* Initiate transfer by reading one of the AO registers. */
+ inb(dev->iobase);
+}
+
+static int pcmda12_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct pcmda12_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->simultaneous_xfer_mode = it->options[1];
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 0x0fff;
+ s->range_table = &pcmda12_ranges;
+ s->insn_write = pcmda12_ao_insn_write;
+ s->insn_read = pcmda12_ao_insn_read;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ pcmda12_ao_reset(dev, s);
+
+ return 0;
+}
+
+static struct comedi_driver pcmda12_driver = {
+ .driver_name = "pcmda12",
+ .module = THIS_MODULE,
+ .attach = pcmda12_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(pcmda12_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcmmio.c b/drivers/staging/comedi/drivers/pcmmio.c
new file mode 100644
index 000000000..10472e6dd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmmio.c
@@ -0,0 +1,786 @@
+/*
+ * pcmmio.c
+ * Driver for Winsystems PC-104 based multifunction IO board.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2007 Calin A. Culianu <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: pcmmio
+ * Description: A driver for the PCM-MIO multifunction board
+ * Devices: [Winsystems] PCM-MIO (pcmmio)
+ * Author: Calin Culianu <calin@ajvar.org>
+ * Updated: Wed, May 16 2007 16:21:10 -0500
+ * Status: works
+ *
+ * A driver for the PCM-MIO multifunction board from Winsystems. This
+ * is a PC-104 based I/O board. It contains four subdevices:
+ *
+ * subdevice 0 - 16 channels of 16-bit AI
+ * subdevice 1 - 8 channels of 16-bit AO
+ * subdevice 2 - first 24 channels of the 48 channel of DIO
+ * (with edge-triggered interrupt support)
+ * subdevice 3 - last 24 channels of the 48 channel DIO
+ * (no interrupt support for this bank of channels)
+ *
+ * Some notes:
+ *
+ * Synchronous reads and writes are the only things implemented for analog
+ * input and output. The hardware itself can do streaming acquisition, etc.
+ *
+ * Asynchronous I/O for the DIO subdevices *is* implemented, however! They
+ * are basically edge-triggered interrupts for any configuration of the
+ * channels in subdevice 2.
+ *
+ * Also note that this interrupt support is untested.
+ *
+ * A few words about edge-detection IRQ support (commands on DIO):
+ *
+ * To use edge-detection IRQ support for the DIO subdevice, pass the IRQ
+ * of the board to the comedi_config command. The board IRQ is not jumpered
+ * but rather configured through software, so any IRQ from 1-15 is OK.
+ *
+ * Due to the genericity of the comedi API, you need to create a special
+ * comedi_command in order to use edge-triggered interrupts for DIO.
+ *
+ * Use comedi_commands with TRIG_NOW. Your callback will be called each
+ * time an edge is detected on the specified DIO line(s), and the data
+ * values will be two sample_t's, which should be concatenated to form
+ * one 32-bit unsigned int. This value is the mask of channels that had
+ * edges detected from your channel list. Note that the bits positions
+ * in the mask correspond to positions in your chanlist when you
+ * specified the command and *not* channel id's!
+ *
+ * To set the polarity of the edge-detection interrupts pass a nonzero value
+ * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero
+ * value for both CR_RANGE and CR_AREF if you want edge-down polarity.
+ *
+ * Configuration Options:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional -- for edge-detect interrupt support only,
+ * leave out if you don't need this feature)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define PCMMIO_AI_LSB_REG 0x00
+#define PCMMIO_AI_MSB_REG 0x01
+#define PCMMIO_AI_CMD_REG 0x02
+#define PCMMIO_AI_CMD_SE (1 << 7)
+#define PCMMIO_AI_CMD_ODD_CHAN (1 << 6)
+#define PCMMIO_AI_CMD_CHAN_SEL(x) (((x) & 0x3) << 4)
+#define PCMMIO_AI_CMD_RANGE(x) (((x) & 0x3) << 2)
+#define PCMMIO_RESOURCE_REG 0x02
+#define PCMMIO_RESOURCE_IRQ(x) (((x) & 0xf) << 0)
+#define PCMMIO_AI_STATUS_REG 0x03
+#define PCMMIO_AI_STATUS_DATA_READY (1 << 7)
+#define PCMMIO_AI_STATUS_DATA_DMA_PEND (1 << 6)
+#define PCMMIO_AI_STATUS_CMD_DMA_PEND (1 << 5)
+#define PCMMIO_AI_STATUS_IRQ_PEND (1 << 4)
+#define PCMMIO_AI_STATUS_DATA_DRQ_ENA (1 << 2)
+#define PCMMIO_AI_STATUS_REG_SEL (1 << 3)
+#define PCMMIO_AI_STATUS_CMD_DRQ_ENA (1 << 1)
+#define PCMMIO_AI_STATUS_IRQ_ENA (1 << 0)
+#define PCMMIO_AI_RES_ENA_REG 0x03
+#define PCMMIO_AI_RES_ENA_CMD_REG_ACCESS (0 << 3)
+#define PCMMIO_AI_RES_ENA_AI_RES_ACCESS (1 << 3)
+#define PCMMIO_AI_RES_ENA_DIO_RES_ACCESS (1 << 4)
+#define PCMMIO_AI_2ND_ADC_OFFSET 0x04
+
+#define PCMMIO_AO_LSB_REG 0x08
+#define PCMMIO_AO_LSB_SPAN(x) (((x) & 0xf) << 0)
+#define PCMMIO_AO_MSB_REG 0x09
+#define PCMMIO_AO_CMD_REG 0x0a
+#define PCMMIO_AO_CMD_WR_SPAN (0x2 << 4)
+#define PCMMIO_AO_CMD_WR_CODE (0x3 << 4)
+#define PCMMIO_AO_CMD_UPDATE (0x4 << 4)
+#define PCMMIO_AO_CMD_UPDATE_ALL (0x5 << 4)
+#define PCMMIO_AO_CMD_WR_SPAN_UPDATE (0x6 << 4)
+#define PCMMIO_AO_CMD_WR_CODE_UPDATE (0x7 << 4)
+#define PCMMIO_AO_CMD_WR_SPAN_UPDATE_ALL (0x8 << 4)
+#define PCMMIO_AO_CMD_WR_CODE_UPDATE_ALL (0x9 << 4)
+#define PCMMIO_AO_CMD_RD_B1_SPAN (0xa << 4)
+#define PCMMIO_AO_CMD_RD_B1_CODE (0xb << 4)
+#define PCMMIO_AO_CMD_RD_B2_SPAN (0xc << 4)
+#define PCMMIO_AO_CMD_RD_B2_CODE (0xd << 4)
+#define PCMMIO_AO_CMD_NOP (0xf << 4)
+#define PCMMIO_AO_CMD_CHAN_SEL(x) (((x) & 0x03) << 1)
+#define PCMMIO_AO_CMD_CHAN_SEL_ALL (0x0f << 0)
+#define PCMMIO_AO_STATUS_REG 0x0b
+#define PCMMIO_AO_STATUS_DATA_READY (1 << 7)
+#define PCMMIO_AO_STATUS_DATA_DMA_PEND (1 << 6)
+#define PCMMIO_AO_STATUS_CMD_DMA_PEND (1 << 5)
+#define PCMMIO_AO_STATUS_IRQ_PEND (1 << 4)
+#define PCMMIO_AO_STATUS_DATA_DRQ_ENA (1 << 2)
+#define PCMMIO_AO_STATUS_REG_SEL (1 << 3)
+#define PCMMIO_AO_STATUS_CMD_DRQ_ENA (1 << 1)
+#define PCMMIO_AO_STATUS_IRQ_ENA (1 << 0)
+#define PCMMIO_AO_RESOURCE_ENA_REG 0x0b
+#define PCMMIO_AO_2ND_DAC_OFFSET 0x04
+
+/*
+ * WinSystems WS16C48
+ *
+ * Offset Page 0 Page 1 Page 2 Page 3
+ * ------ ----------- ----------- ----------- -----------
+ * 0x10 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O
+ * 0x11 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O
+ * 0x12 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O
+ * 0x13 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O
+ * 0x14 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O
+ * 0x15 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O
+ * 0x16 INT_PENDING INT_PENDING INT_PENDING INT_PENDING
+ * 0x17 Page/Lock Page/Lock Page/Lock Page/Lock
+ * 0x18 N/A POL_0 ENAB_0 INT_ID0
+ * 0x19 N/A POL_1 ENAB_1 INT_ID1
+ * 0x1a N/A POL_2 ENAB_2 INT_ID2
+ */
+#define PCMMIO_PORT_REG(x) (0x10 + (x))
+#define PCMMIO_INT_PENDING_REG 0x16
+#define PCMMIO_PAGE_LOCK_REG 0x17
+#define PCMMIO_LOCK_PORT(x) ((1 << (x)) & 0x3f)
+#define PCMMIO_PAGE(x) (((x) & 0x3) << 6)
+#define PCMMIO_PAGE_MASK PCMUIO_PAGE(3)
+#define PCMMIO_PAGE_POL 1
+#define PCMMIO_PAGE_ENAB 2
+#define PCMMIO_PAGE_INT_ID 3
+#define PCMMIO_PAGE_REG(x) (0x18 + (x))
+
+static const struct comedi_lrange pcmmio_ai_ranges = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+static const struct comedi_lrange pcmmio_ao_ranges = {
+ 6, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ BIP_RANGE(2.5),
+ RANGE(-2.5, 7.5)
+ }
+};
+
+struct pcmmio_private {
+ spinlock_t pagelock; /* protects the page registers */
+ spinlock_t spinlock; /* protects the member variables */
+ unsigned int enabled_mask;
+ unsigned int active:1;
+};
+
+static void pcmmio_dio_write(struct comedi_device *dev, unsigned int val,
+ int page, int port)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned long iobase = dev->iobase;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ outb(val & 0xff, iobase + PCMMIO_PORT_REG(port + 0));
+ outb((val >> 8) & 0xff, iobase + PCMMIO_PORT_REG(port + 1));
+ outb((val >> 16) & 0xff, iobase + PCMMIO_PORT_REG(port + 2));
+ } else {
+ outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG);
+ outb(val & 0xff, iobase + PCMMIO_PAGE_REG(0));
+ outb((val >> 8) & 0xff, iobase + PCMMIO_PAGE_REG(1));
+ outb((val >> 16) & 0xff, iobase + PCMMIO_PAGE_REG(2));
+ }
+ spin_unlock_irqrestore(&devpriv->pagelock, flags);
+}
+
+static unsigned int pcmmio_dio_read(struct comedi_device *dev,
+ int page, int port)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned long iobase = dev->iobase;
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&devpriv->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ val = inb(iobase + PCMMIO_PORT_REG(port + 0));
+ val |= (inb(iobase + PCMMIO_PORT_REG(port + 1)) << 8);
+ val |= (inb(iobase + PCMMIO_PORT_REG(port + 2)) << 16);
+ } else {
+ outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG);
+ val = inb(iobase + PCMMIO_PAGE_REG(0));
+ val |= (inb(iobase + PCMMIO_PAGE_REG(1)) << 8);
+ val |= (inb(iobase + PCMMIO_PAGE_REG(2)) << 16);
+ }
+ spin_unlock_irqrestore(&devpriv->pagelock, flags);
+
+ return val;
+}
+
+/*
+ * Each channel can be individually programmed for input or output.
+ * Writing a '0' to a channel causes the corresponding output pin
+ * to go to a high-z state (pulled high by an external 10K resistor).
+ * This allows it to be used as an input. When used in the input mode,
+ * a read reflects the inverted state of the I/O pin, such that a
+ * high on the pin will read as a '0' in the register. Writing a '1'
+ * to a bit position causes the pin to sink current (up to 12mA),
+ * effectively pulling it low.
+ */
+static int pcmmio_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */
+ int port = s->index == 2 ? 0 : 3;
+ unsigned int chanmask = (1 << s->n_chan) - 1;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ /*
+ * Outputs are inverted, invert the state and
+ * update the channels.
+ *
+ * The s->io_bits mask makes sure the input channels
+ * are '0' so that the outputs pins stay in a high
+ * z-state.
+ */
+ val = ~s->state & chanmask;
+ val &= s->io_bits;
+ pcmmio_dio_write(dev, val, 0, port);
+ }
+
+ /* get inverted state of the channels from the port */
+ val = pcmmio_dio_read(dev, 0, port);
+
+ /* return the true state of the channels */
+ data[1] = ~val & chanmask;
+
+ return insn->n;
+}
+
+static int pcmmio_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */
+ int port = s->index == 2 ? 0 : 3;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ if (data[0] == INSN_CONFIG_DIO_INPUT)
+ pcmmio_dio_write(dev, s->io_bits, 0, port);
+
+ return insn->n;
+}
+
+static void pcmmio_reset(struct comedi_device *dev)
+{
+ /* Clear all the DIO port bits */
+ pcmmio_dio_write(dev, 0, 0, 0);
+ pcmmio_dio_write(dev, 0, 0, 3);
+
+ /* Clear all the paged registers */
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_POL, 0);
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0);
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0);
+}
+
+/* devpriv->spinlock is already locked */
+static void pcmmio_stop_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmmio_private *devpriv = dev->private;
+
+ devpriv->enabled_mask = 0;
+ devpriv->active = 0;
+ s->async->inttrig = NULL;
+
+ /* disable all dio interrupts */
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0);
+}
+
+static void pcmmio_handle_dio_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int triggered)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int val = 0;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+
+ if (!devpriv->active)
+ goto done;
+
+ if (!(triggered & devpriv->enabled_mask))
+ goto done;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (triggered & (1 << chan))
+ val |= (1 << i);
+ }
+
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+
+done:
+ spin_unlock_irqrestore(&devpriv->spinlock, flags);
+
+ comedi_handle_events(dev, s);
+}
+
+static irqreturn_t interrupt_pcmmio(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int triggered;
+ unsigned char int_pend;
+
+ /* are there any interrupts pending */
+ int_pend = inb(dev->iobase + PCMMIO_INT_PENDING_REG) & 0x07;
+ if (!int_pend)
+ return IRQ_NONE;
+
+ /* get, and clear, the pending interrupts */
+ triggered = pcmmio_dio_read(dev, PCMMIO_PAGE_INT_ID, 0);
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0);
+
+ pcmmio_handle_dio_intr(dev, s, triggered);
+
+ return IRQ_HANDLED;
+}
+
+/* devpriv->spinlock is already locked */
+static void pcmmio_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int bits = 0;
+ unsigned int pol_bits = 0;
+ int i;
+
+ devpriv->enabled_mask = 0;
+ devpriv->active = 1;
+ if (cmd->chanlist) {
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chanspec = cmd->chanlist[i];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+
+ bits |= (1 << chan);
+ pol_bits |= (((aref || range) ? 1 : 0) << chan);
+ }
+ }
+ bits &= ((1 << s->n_chan) - 1);
+ devpriv->enabled_mask = bits;
+
+ /* set polarity and enable interrupts */
+ pcmmio_dio_write(dev, pol_bits, PCMMIO_PAGE_POL, 0);
+ pcmmio_dio_write(dev, bits, PCMMIO_PAGE_ENAB, 0);
+}
+
+static int pcmmio_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+ if (devpriv->active)
+ pcmmio_stop_intr(dev, s);
+ spin_unlock_irqrestore(&devpriv->spinlock, flags);
+
+ return 0;
+}
+
+static int pcmmio_inttrig_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+ s->async->inttrig = NULL;
+ if (devpriv->active)
+ pcmmio_start_intr(dev, s);
+ spin_unlock_irqrestore(&devpriv->spinlock, flags);
+
+ return 1;
+}
+
+/*
+ * 'do_cmd' function for an 'INTERRUPT' subdevice.
+ */
+static int pcmmio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+ devpriv->active = 1;
+
+ /* Set up start of acquisition. */
+ if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = pcmmio_inttrig_start_intr;
+ else /* TRIG_NOW */
+ pcmmio_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&devpriv->spinlock, flags);
+
+ return 0;
+}
+
+static int pcmmio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ /* if (err) return 4; */
+
+ return 0;
+}
+
+static int pcmmio_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + PCMMIO_AI_STATUS_REG);
+ if (status & PCMMIO_AI_STATUS_DATA_READY)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcmmio_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long iobase = dev->iobase;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned char cmd = 0;
+ unsigned int val;
+ int ret;
+ int i;
+
+ /*
+ * The PCM-MIO uses two Linear Tech LTC1859CG 8-channel A/D converters.
+ * The devices use a full duplex serial interface which transmits and
+ * receives data simultaneously. An 8-bit command is shifted into the
+ * ADC interface to configure it for the next conversion. At the same
+ * time, the data from the previous conversion is shifted out of the
+ * device. Consequently, the conversion result is delayed by one
+ * conversion from the command word.
+ *
+ * Setup the cmd for the conversions then do a dummy conversion to
+ * flush the junk data. Then do each conversion requested by the
+ * comedi_insn. Note that the last conversion will leave junk data
+ * in ADC which will get flushed on the next comedi_insn.
+ */
+
+ if (chan > 7) {
+ chan -= 8;
+ iobase += PCMMIO_AI_2ND_ADC_OFFSET;
+ }
+
+ if (aref == AREF_GROUND)
+ cmd |= PCMMIO_AI_CMD_SE;
+ if (chan % 2)
+ cmd |= PCMMIO_AI_CMD_ODD_CHAN;
+ cmd |= PCMMIO_AI_CMD_CHAN_SEL(chan / 2);
+ cmd |= PCMMIO_AI_CMD_RANGE(range);
+
+ outb(cmd, iobase + PCMMIO_AI_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(iobase + PCMMIO_AI_LSB_REG);
+ val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8;
+
+ for (i = 0; i < insn->n; i++) {
+ outb(cmd, iobase + PCMMIO_AI_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(iobase + PCMMIO_AI_LSB_REG);
+ val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8;
+
+ /* bipolar data is two's complement */
+ if (comedi_range_is_bipolar(s, range))
+ val = comedi_offset_munge(s, val);
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int pcmmio_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + PCMMIO_AO_STATUS_REG);
+ if (status & PCMMIO_AO_STATUS_DATA_READY)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcmmio_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long iobase = dev->iobase;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned char cmd = 0;
+ int ret;
+ int i;
+
+ /*
+ * The PCM-MIO has two Linear Tech LTC2704 DAC devices. Each device
+ * is a 4-channel converter with software-selectable output range.
+ */
+
+ if (chan > 3) {
+ cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan - 4);
+ iobase += PCMMIO_AO_2ND_DAC_OFFSET;
+ } else {
+ cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan);
+ }
+
+ /* set the range for the channel */
+ outb(PCMMIO_AO_LSB_SPAN(range), iobase + PCMMIO_AO_LSB_REG);
+ outb(0, iobase + PCMMIO_AO_MSB_REG);
+ outb(cmd | PCMMIO_AO_CMD_WR_SPAN_UPDATE, iobase + PCMMIO_AO_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ /* write the data to the channel */
+ outb(val & 0xff, iobase + PCMMIO_AO_LSB_REG);
+ outb((val >> 8) & 0xff, iobase + PCMMIO_AO_MSB_REG);
+ outb(cmd | PCMMIO_AO_CMD_WR_CODE_UPDATE,
+ iobase + PCMMIO_AO_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ s->readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int pcmmio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct pcmmio_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 32);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->pagelock);
+ spin_lock_init(&devpriv->spinlock);
+
+ pcmmio_reset(dev);
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], interrupt_pcmmio, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = it->options[1];
+
+ /* configure the interrupt routing on the board */
+ outb(PCMMIO_AI_RES_ENA_DIO_RES_ACCESS,
+ dev->iobase + PCMMIO_AI_RES_ENA_REG);
+ outb(PCMMIO_RESOURCE_IRQ(dev->irq),
+ dev->iobase + PCMMIO_RESOURCE_REG);
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0xffff;
+ s->range_table = &pcmmio_ai_ranges;
+ s->insn_read = pcmmio_ai_insn_read;
+
+ /* initialize the resource enable register by clearing it */
+ outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS,
+ dev->iobase + PCMMIO_AI_RES_ENA_REG);
+ outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS,
+ dev->iobase + PCMMIO_AI_RES_ENA_REG + PCMMIO_AI_2ND_ADC_OFFSET);
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 0xffff;
+ s->range_table = &pcmmio_ao_ranges;
+ s->insn_write = pcmmio_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* initialize the resource enable register by clearing it */
+ outb(0, dev->iobase + PCMMIO_AO_RESOURCE_ENA_REG);
+ outb(0, dev->iobase + PCMMIO_AO_2ND_DAC_OFFSET +
+ PCMMIO_AO_RESOURCE_ENA_REG);
+
+ /* Digital I/O subdevice with interrupt support */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->len_chanlist = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcmmio_dio_insn_bits;
+ s->insn_config = pcmmio_dio_insn_config;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL | SDF_PACKED;
+ s->len_chanlist = s->n_chan;
+ s->cancel = pcmmio_cancel;
+ s->do_cmd = pcmmio_cmd;
+ s->do_cmdtest = pcmmio_cmdtest;
+ }
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcmmio_dio_insn_bits;
+ s->insn_config = pcmmio_dio_insn_config;
+
+ return 0;
+}
+
+static struct comedi_driver pcmmio_driver = {
+ .driver_name = "pcmmio",
+ .module = THIS_MODULE,
+ .attach = pcmmio_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(pcmmio_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Winsystems PCM-MIO PC/104 board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcmuio.c b/drivers/staging/comedi/drivers/pcmuio.c
new file mode 100644
index 000000000..7ea813022
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmuio.c
@@ -0,0 +1,633 @@
+/*
+ * pcmuio.c
+ * Comedi driver for Winsystems PC-104 based 48/96-channel DIO boards.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: pcmuio
+ * Description: Winsystems PC-104 based 48/96-channel DIO boards.
+ * Devices: [Winsystems] PCM-UIO48A (pcmuio48), PCM-UIO96A (pcmuio96)
+ * Author: Calin Culianu <calin@ajvar.org>
+ * Updated: Fri, 13 Jan 2006 12:01:01 -0500
+ * Status: works
+ *
+ * A driver for the relatively straightforward-to-program PCM-UIO48A and
+ * PCM-UIO96A boards from Winsystems. These boards use either one or two
+ * (in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). This
+ * chip is interesting in that each I/O line is individually programmable
+ * for INPUT or OUTPUT (thus comedi_dio_config can be done on a per-channel
+ * basis). Also, each chip supports edge-triggered interrupts for the first
+ * 24 I/O lines. Of course, since the 96-channel version of the board has
+ * two ASICs, it can detect polarity changes on up to 48 I/O lines. Since
+ * this is essentially an (non-PnP) ISA board, I/O Address and IRQ selection
+ * are done through jumpers on the board. You need to pass that information
+ * to this driver as the first and second comedi_config option, respectively.
+ * Note that the 48-channel version uses 16 bytes of IO memory and the 96-
+ * channel version uses 32-bytes (in case you are worried about conflicts).
+ * The 48-channel board is split into two 24-channel comedi subdevices. The
+ * 96-channel board is split into 4 24-channel DIO subdevices.
+ *
+ * Note that IRQ support has been added, but it is untested.
+ *
+ * To use edge-detection IRQ support, pass the IRQs of both ASICS (for the
+ * 96 channel version) or just 1 ASIC (for 48-channel version). Then, use
+ * comedi_commands with TRIG_NOW. Your callback will be called each time an
+ * edge is triggered, and the data values will be two sample_t's, which
+ * should be concatenated to form one 32-bit unsigned int. This value is
+ * the mask of channels that had edges detected from your channel list. Note
+ * that the bits positions in the mask correspond to positions in your
+ * chanlist when you specified the command and *not* channel id's!
+ *
+ * To set the polarity of the edge-detection interrupts pass a nonzero value
+ * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for
+ * both CR_RANGE and CR_AREF if you want edge-down polarity.
+ *
+ * In the 48-channel version:
+ *
+ * On subdev 0, the first 24 channels channels are edge-detect channels.
+ *
+ * In the 96-channel board you have the following channels that can do edge
+ * detection:
+ *
+ * subdev 0, channels 0-24 (first 24 channels of 1st ASIC)
+ * subdev 2, channels 0-24 (first 24 channels of 2nd ASIC)
+ *
+ * Configuration Options:
+ * [0] - I/O port base address
+ * [1] - IRQ (for first ASIC, or first 24 channels)
+ * [2] - IRQ (for second ASIC, pcmuio96 only - IRQ for chans 48-72
+ * can be the same as first irq!)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ *
+ * Offset Page 0 Page 1 Page 2 Page 3
+ * ------ ----------- ----------- ----------- -----------
+ * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O
+ * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O
+ * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O
+ * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O
+ * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O
+ * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O
+ * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING
+ * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock
+ * 0x08 N/A POL_0 ENAB_0 INT_ID0
+ * 0x09 N/A POL_1 ENAB_1 INT_ID1
+ * 0x0a N/A POL_2 ENAB_2 INT_ID2
+ */
+#define PCMUIO_PORT_REG(x) (0x00 + (x))
+#define PCMUIO_INT_PENDING_REG 0x06
+#define PCMUIO_PAGE_LOCK_REG 0x07
+#define PCMUIO_LOCK_PORT(x) ((1 << (x)) & 0x3f)
+#define PCMUIO_PAGE(x) (((x) & 0x3) << 6)
+#define PCMUIO_PAGE_MASK PCMUIO_PAGE(3)
+#define PCMUIO_PAGE_POL 1
+#define PCMUIO_PAGE_ENAB 2
+#define PCMUIO_PAGE_INT_ID 3
+#define PCMUIO_PAGE_REG(x) (0x08 + (x))
+
+#define PCMUIO_ASIC_IOSIZE 0x10
+#define PCMUIO_MAX_ASICS 2
+
+struct pcmuio_board {
+ const char *name;
+ const int num_asics;
+};
+
+static const struct pcmuio_board pcmuio_boards[] = {
+ {
+ .name = "pcmuio48",
+ .num_asics = 1,
+ }, {
+ .name = "pcmuio96",
+ .num_asics = 2,
+ },
+};
+
+struct pcmuio_asic {
+ spinlock_t pagelock; /* protects the page registers */
+ spinlock_t spinlock; /* protects member variables */
+ unsigned int enabled_mask;
+ unsigned int active:1;
+};
+
+struct pcmuio_private {
+ struct pcmuio_asic asics[PCMUIO_MAX_ASICS];
+ unsigned int irq2;
+};
+
+static inline unsigned long pcmuio_asic_iobase(struct comedi_device *dev,
+ int asic)
+{
+ return dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
+}
+
+static inline int pcmuio_subdevice_to_asic(struct comedi_subdevice *s)
+{
+ /*
+ * subdevice 0 and 1 are handled by the first asic
+ * subdevice 2 and 3 are handled by the second asic
+ */
+ return s->index / 2;
+}
+
+static inline int pcmuio_subdevice_to_port(struct comedi_subdevice *s)
+{
+ /*
+ * subdevice 0 and 2 use port registers 0-2
+ * subdevice 1 and 3 use port registers 3-5
+ */
+ return (s->index % 2) ? 3 : 0;
+}
+
+static void pcmuio_write(struct comedi_device *dev, unsigned int val,
+ int asic, int page, int port)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long iobase = pcmuio_asic_iobase(dev, asic);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ outb(val & 0xff, iobase + PCMUIO_PORT_REG(port + 0));
+ outb((val >> 8) & 0xff, iobase + PCMUIO_PORT_REG(port + 1));
+ outb((val >> 16) & 0xff, iobase + PCMUIO_PORT_REG(port + 2));
+ } else {
+ outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
+ outb(val & 0xff, iobase + PCMUIO_PAGE_REG(0));
+ outb((val >> 8) & 0xff, iobase + PCMUIO_PAGE_REG(1));
+ outb((val >> 16) & 0xff, iobase + PCMUIO_PAGE_REG(2));
+ }
+ spin_unlock_irqrestore(&chip->pagelock, flags);
+}
+
+static unsigned int pcmuio_read(struct comedi_device *dev,
+ int asic, int page, int port)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long iobase = pcmuio_asic_iobase(dev, asic);
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&chip->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ val = inb(iobase + PCMUIO_PORT_REG(port + 0));
+ val |= (inb(iobase + PCMUIO_PORT_REG(port + 1)) << 8);
+ val |= (inb(iobase + PCMUIO_PORT_REG(port + 2)) << 16);
+ } else {
+ outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
+ val = inb(iobase + PCMUIO_PAGE_REG(0));
+ val |= (inb(iobase + PCMUIO_PAGE_REG(1)) << 8);
+ val |= (inb(iobase + PCMUIO_PAGE_REG(2)) << 16);
+ }
+ spin_unlock_irqrestore(&chip->pagelock, flags);
+
+ return val;
+}
+
+/*
+ * Each channel can be individually programmed for input or output.
+ * Writing a '0' to a channel causes the corresponding output pin
+ * to go to a high-z state (pulled high by an external 10K resistor).
+ * This allows it to be used as an input. When used in the input mode,
+ * a read reflects the inverted state of the I/O pin, such that a
+ * high on the pin will read as a '0' in the register. Writing a '1'
+ * to a bit position causes the pin to sink current (up to 12mA),
+ * effectively pulling it low.
+ */
+static int pcmuio_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int asic = pcmuio_subdevice_to_asic(s);
+ int port = pcmuio_subdevice_to_port(s);
+ unsigned int chanmask = (1 << s->n_chan) - 1;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ /*
+ * Outputs are inverted, invert the state and
+ * update the channels.
+ *
+ * The s->io_bits mask makes sure the input channels
+ * are '0' so that the outputs pins stay in a high
+ * z-state.
+ */
+ val = ~s->state & chanmask;
+ val &= s->io_bits;
+ pcmuio_write(dev, val, asic, 0, port);
+ }
+
+ /* get inverted state of the channels from the port */
+ val = pcmuio_read(dev, asic, 0, port);
+
+ /* return the true state of the channels */
+ data[1] = ~val & chanmask;
+
+ return insn->n;
+}
+
+static int pcmuio_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int asic = pcmuio_subdevice_to_asic(s);
+ int port = pcmuio_subdevice_to_port(s);
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ if (data[0] == INSN_CONFIG_DIO_INPUT)
+ pcmuio_write(dev, s->io_bits, asic, 0, port);
+
+ return insn->n;
+}
+
+static void pcmuio_reset(struct comedi_device *dev)
+{
+ const struct pcmuio_board *board = dev->board_ptr;
+ int asic;
+
+ for (asic = 0; asic < board->num_asics; ++asic) {
+ /* first, clear all the DIO port bits */
+ pcmuio_write(dev, 0, asic, 0, 0);
+ pcmuio_write(dev, 0, asic, 0, 3);
+
+ /* Next, clear all the paged registers for each page */
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_POL, 0);
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
+ }
+}
+
+/* chip->spinlock is already locked */
+static void pcmuio_stop_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+
+ chip->enabled_mask = 0;
+ chip->active = 0;
+ s->async->inttrig = NULL;
+
+ /* disable all intrs for this subdev.. */
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
+}
+
+static void pcmuio_handle_intr_subdev(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned triggered)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int val = 0;
+ unsigned long flags;
+ unsigned int i;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+
+ if (!chip->active)
+ goto done;
+
+ if (!(triggered & chip->enabled_mask))
+ goto done;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (triggered & (1 << chan))
+ val |= (1 << i);
+ }
+
+ comedi_buf_write_samples(s, &val, 1);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+
+done:
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ comedi_handle_events(dev, s);
+}
+
+static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic)
+{
+ /* there are could be two asics so we can't use dev->read_subdev */
+ struct comedi_subdevice *s = &dev->subdevices[asic * 2];
+ unsigned long iobase = pcmuio_asic_iobase(dev, asic);
+ unsigned int val;
+
+ /* are there any interrupts pending */
+ val = inb(iobase + PCMUIO_INT_PENDING_REG) & 0x07;
+ if (!val)
+ return 0;
+
+ /* get, and clear, the pending interrupts */
+ val = pcmuio_read(dev, asic, PCMUIO_PAGE_INT_ID, 0);
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
+
+ /* handle the pending interrupts */
+ pcmuio_handle_intr_subdev(dev, s, val);
+
+ return 1;
+}
+
+static irqreturn_t pcmuio_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pcmuio_private *devpriv = dev->private;
+ int handled = 0;
+
+ if (irq == dev->irq)
+ handled += pcmuio_handle_asic_interrupt(dev, 0);
+ if (irq == devpriv->irq2)
+ handled += pcmuio_handle_asic_interrupt(dev, 1);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/* chip->spinlock is already locked */
+static void pcmuio_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int bits = 0;
+ unsigned int pol_bits = 0;
+ int i;
+
+ chip->enabled_mask = 0;
+ chip->active = 1;
+ if (cmd->chanlist) {
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chanspec = cmd->chanlist[i];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+
+ bits |= (1 << chan);
+ pol_bits |= ((aref || range) ? 1 : 0) << chan;
+ }
+ }
+ bits &= ((1 << s->n_chan) - 1);
+ chip->enabled_mask = bits;
+
+ /* set pol and enab intrs for this subdev.. */
+ pcmuio_write(dev, pol_bits, asic, PCMUIO_PAGE_POL, 0);
+ pcmuio_write(dev, bits, asic, PCMUIO_PAGE_ENAB, 0);
+}
+
+static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ if (chip->active)
+ pcmuio_stop_intr(dev, s);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static int pcmuio_inttrig_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long flags;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ s->async->inttrig = NULL;
+ if (chip->active)
+ pcmuio_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 1;
+}
+
+/*
+ * 'do_cmd' function for an 'INTERRUPT' subdevice.
+ */
+static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ chip->active = 1;
+
+ /* Set up start of acquisition. */
+ if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = pcmuio_inttrig_start_intr;
+ else /* TRIG_NOW */
+ pcmuio_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static int pcmuio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ /* if (err) return 4; */
+
+ return 0;
+}
+
+static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcmuio_board *board = dev->board_ptr;
+ struct comedi_subdevice *s;
+ struct pcmuio_private *devpriv;
+ int ret;
+ int i;
+
+ ret = comedi_request_region(dev, it->options[0],
+ board->num_asics * PCMUIO_ASIC_IOSIZE);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ for (i = 0; i < PCMUIO_MAX_ASICS; ++i) {
+ struct pcmuio_asic *chip = &devpriv->asics[i];
+
+ spin_lock_init(&chip->pagelock);
+ spin_lock_init(&chip->spinlock);
+ }
+
+ pcmuio_reset(dev);
+
+ if (it->options[1]) {
+ /* request the irq for the 1st asic */
+ ret = request_irq(it->options[1], pcmuio_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ if (board->num_asics == 2) {
+ if (it->options[2] == dev->irq) {
+ /* the same irq (or none) is used by both asics */
+ devpriv->irq2 = it->options[2];
+ } else if (it->options[2]) {
+ /* request the irq for the 2nd asic */
+ ret = request_irq(it->options[2], pcmuio_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ devpriv->irq2 = it->options[2];
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, board->num_asics * 2);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; ++i) {
+ s = &dev->subdevices[i];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcmuio_dio_insn_bits;
+ s->insn_config = pcmuio_dio_insn_config;
+
+ /* subdevices 0 and 2 can support interrupts */
+ if ((i == 0 && dev->irq) || (i == 2 && devpriv->irq2)) {
+ /* setup the interrupt subdevice */
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL |
+ SDF_PACKED;
+ s->len_chanlist = s->n_chan;
+ s->cancel = pcmuio_cancel;
+ s->do_cmd = pcmuio_cmd;
+ s->do_cmdtest = pcmuio_cmdtest;
+ }
+ }
+
+ return 0;
+}
+
+static void pcmuio_detach(struct comedi_device *dev)
+{
+ struct pcmuio_private *devpriv = dev->private;
+
+ if (devpriv) {
+ pcmuio_reset(dev);
+
+ /* free the 2nd irq if used, the core will free the 1st one */
+ if (devpriv->irq2 && devpriv->irq2 != dev->irq)
+ free_irq(devpriv->irq2, dev);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcmuio_driver = {
+ .driver_name = "pcmuio",
+ .module = THIS_MODULE,
+ .attach = pcmuio_attach,
+ .detach = pcmuio_detach,
+ .board_name = &pcmuio_boards[0].name,
+ .offset = sizeof(struct pcmuio_board),
+ .num_names = ARRAY_SIZE(pcmuio_boards),
+};
+module_comedi_driver(pcmuio_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/plx9052.h b/drivers/staging/comedi/drivers/plx9052.h
new file mode 100644
index 000000000..fbcf25069
--- /dev/null
+++ b/drivers/staging/comedi/drivers/plx9052.h
@@ -0,0 +1,79 @@
+/*
+ comedi/drivers/plx9052.h
+ Definitions for the PLX-9052 PCI interface chip
+
+ Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef _PLX9052_H_
+#define _PLX9052_H_
+
+/*
+ * INTCSR - Interrupt Control/Status register
+ */
+#define PLX9052_INTCSR 0x4c
+#define PLX9052_INTCSR_LI1ENAB (1 << 0) /* LI1 enabled */
+#define PLX9052_INTCSR_LI1POL (1 << 1) /* LI1 active high */
+#define PLX9052_INTCSR_LI1STAT (1 << 2) /* LI1 active */
+#define PLX9052_INTCSR_LI2ENAB (1 << 3) /* LI2 enabled */
+#define PLX9052_INTCSR_LI2POL (1 << 4) /* LI2 active high */
+#define PLX9052_INTCSR_LI2STAT (1 << 5) /* LI2 active */
+#define PLX9052_INTCSR_PCIENAB (1 << 6) /* PCIINT enabled */
+#define PLX9052_INTCSR_SOFTINT (1 << 7) /* generate soft int */
+#define PLX9052_INTCSR_LI1SEL (1 << 8) /* LI1 edge */
+#define PLX9052_INTCSR_LI2SEL (1 << 9) /* LI2 edge */
+#define PLX9052_INTCSR_LI1CLRINT (1 << 10) /* LI1 clear int */
+#define PLX9052_INTCSR_LI2CLRINT (1 << 11) /* LI2 clear int */
+#define PLX9052_INTCSR_ISAMODE (1 << 12) /* ISA interface mode */
+
+/*
+ * CNTRL - User I/O, Direct Slave Response, Serial EEPROM, and
+ * Initialization Control register
+ */
+#define PLX9052_CNTRL 0x50
+#define PLX9052_CNTRL_WAITO (1 << 0) /* UIO0 or WAITO# select */
+#define PLX9052_CNTRL_UIO0_DIR (1 << 1) /* UIO0 direction */
+#define PLX9052_CNTRL_UIO0_DATA (1 << 2) /* UIO0 data */
+#define PLX9052_CNTRL_LLOCKO (1 << 3) /* UIO1 or LLOCKo# select */
+#define PLX9052_CNTRL_UIO1_DIR (1 << 4) /* UIO1 direction */
+#define PLX9052_CNTRL_UIO1_DATA (1 << 5) /* UIO1 data */
+#define PLX9052_CNTRL_CS2 (1 << 6) /* UIO2 or CS2# select */
+#define PLX9052_CNTRL_UIO2_DIR (1 << 7) /* UIO2 direction */
+#define PLX9052_CNTRL_UIO2_DATA (1 << 8) /* UIO2 data */
+#define PLX9052_CNTRL_CS3 (1 << 9) /* UIO3 or CS3# select */
+#define PLX9052_CNTRL_UIO3_DIR (1 << 10) /* UIO3 direction */
+#define PLX9052_CNTRL_UIO3_DATA (1 << 11) /* UIO3 data */
+#define PLX9052_CNTRL_PCIBAR01 (0 << 12) /* bar 0 (mem) and 1 (I/O) */
+#define PLX9052_CNTRL_PCIBAR0 (1 << 12) /* bar 0 (mem) only */
+#define PLX9052_CNTRL_PCIBAR1 (2 << 12) /* bar 1 (I/O) only */
+#define PLX9052_CNTRL_PCI2_1_FEATURES (1 << 14) /* PCI r2.1 features enabled */
+#define PLX9052_CNTRL_PCI_R_W_FLUSH (1 << 15) /* read w/write flush mode */
+#define PLX9052_CNTRL_PCI_R_NO_FLUSH (1 << 16) /* read no flush mode */
+#define PLX9052_CNTRL_PCI_R_NO_WRITE (1 << 17) /* read no write mode */
+#define PLX9052_CNTRL_PCI_W_RELEASE (1 << 18) /* write release bus mode */
+#define PLX9052_CNTRL_RETRY_CLKS(x) (((x) & 0xf) << 19) /* slave retry clks */
+#define PLX9052_CNTRL_LOCK_ENAB (1 << 23) /* slave LOCK# enable */
+#define PLX9052_CNTRL_EEPROM_MASK (0x1f << 24) /* EEPROM bits */
+#define PLX9052_CNTRL_EEPROM_CLK (1 << 24) /* EEPROM clock */
+#define PLX9052_CNTRL_EEPROM_CS (1 << 25) /* EEPROM chip select */
+#define PLX9052_CNTRL_EEPROM_DOUT (1 << 26) /* EEPROM write bit */
+#define PLX9052_CNTRL_EEPROM_DIN (1 << 27) /* EEPROM read bit */
+#define PLX9052_CNTRL_EEPROM_PRESENT (1 << 28) /* EEPROM present */
+#define PLX9052_CNTRL_RELOAD_CFG (1 << 29) /* reload configuration */
+#define PLX9052_CNTRL_PCI_RESET (1 << 30) /* PCI adapter reset */
+#define PLX9052_CNTRL_MASK_REV (1 << 31) /* mask revision */
+
+#endif /* _PLX9052_H_ */
diff --git a/drivers/staging/comedi/drivers/plx9080.h b/drivers/staging/comedi/drivers/plx9080.h
new file mode 100644
index 000000000..25706531b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/plx9080.h
@@ -0,0 +1,422 @@
+/* plx9080.h
+ *
+ * Copyright (C) 2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * I modified this file from the plx9060.h header for the
+ * wanXL device driver in the linux kernel,
+ * for the register offsets and bit definitions. Made minor modifications,
+ * added plx9080 registers and
+ * stripped out stuff that was specifically for the wanXL driver.
+ * Note: I've only made sure the definitions are correct as far
+ * as I make use of them. There are still various plx9060-isms
+ * left in this header file.
+ *
+ ********************************************************************
+ *
+ * Copyright (C) 1999 RG Studio s.c.
+ * Written by Krzysztof Halasa <khc@rgstudio.com.pl>
+ *
+ * Portions (C) SBE Inc., used by permission.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef __COMEDI_PLX9080_H
+#define __COMEDI_PLX9080_H
+
+/* descriptor block used for chained dma transfers */
+struct plx_dma_desc {
+ __le32 pci_start_addr;
+ __le32 local_start_addr;
+ /* transfer_size is in bytes, only first 23 bits of register are used */
+ __le32 transfer_size;
+ /* address of next descriptor (quad word aligned), plus some
+ * additional bits (see PLX_DMA0_DESCRIPTOR_REG) */
+ __le32 next;
+};
+
+/**********************************************************************
+** Register Offsets and Bit Definitions
+**
+** Note: All offsets zero relative. IE. Some standard base address
+** must be added to the Register Number to properly access the register.
+**
+**********************************************************************/
+
+#define PLX_LAS0RNG_REG 0x0000 /* L, Local Addr Space 0 Range Register */
+#define PLX_LAS1RNG_REG 0x00f0 /* L, Local Addr Space 1 Range Register */
+#define LRNG_IO 0x00000001 /* Map to: 1=I/O, 0=Mem */
+#define LRNG_ANY32 0x00000000 /* Locate anywhere in 32 bit */
+#define LRNG_LT1MB 0x00000002 /* Locate in 1st meg */
+#define LRNG_ANY64 0x00000004 /* Locate anywhere in 64 bit */
+#define LRNG_MEM_MASK 0xfffffff0 /* bits that specify range for memory io */
+#define LRNG_IO_MASK 0xfffffffa /* bits that specify range for normal io */
+
+#define PLX_LAS0MAP_REG 0x0004 /* L, Local Addr Space 0 Remap Register */
+#define PLX_LAS1MAP_REG 0x00f4 /* L, Local Addr Space 1 Remap Register */
+#define LMAP_EN 0x00000001 /* Enable slave decode */
+#define LMAP_MEM_MASK 0xfffffff0 /* bits that specify decode for memory io */
+#define LMAP_IO_MASK 0xfffffffa /* bits that specify decode bits for normal io */
+
+/* Mode/Arbitration Register.
+*/
+#define PLX_MARB_REG 0x8 /* L, Local Arbitration Register */
+#define PLX_DMAARB_REG 0xac
+enum marb_bits {
+ MARB_LLT_MASK = 0x000000ff, /* Local Bus Latency Timer */
+ MARB_LPT_MASK = 0x0000ff00, /* Local Bus Pause Timer */
+ MARB_LTEN = 0x00010000, /* Latency Timer Enable */
+ MARB_LPEN = 0x00020000, /* Pause Timer Enable */
+ MARB_BREQ = 0x00040000, /* Local Bus BREQ Enable */
+ MARB_DMA_PRIORITY_MASK = 0x00180000,
+ MARB_LBDS_GIVE_UP_BUS_MODE = 0x00200000, /* local bus direct slave give up bus mode */
+ MARB_DS_LLOCK_ENABLE = 0x00400000, /* direct slave LLOCKo# enable */
+ MARB_PCI_REQUEST_MODE = 0x00800000,
+ MARB_PCIv21_MODE = 0x01000000, /* pci specification v2.1 mode */
+ MARB_PCI_READ_NO_WRITE_MODE = 0x02000000,
+ MARB_PCI_READ_WITH_WRITE_FLUSH_MODE = 0x04000000,
+ MARB_GATE_TIMER_WITH_BREQ = 0x08000000, /* gate local bus latency timer with BREQ */
+ MARB_PCI_READ_NO_FLUSH_MODE = 0x10000000,
+ MARB_USE_SUBSYSTEM_IDS = 0x20000000,
+};
+
+#define PLX_BIGEND_REG 0xc
+enum bigend_bits {
+ BIGEND_CONFIG = 0x1, /* use big endian ordering for configuration register accesses */
+ BIGEND_DIRECT_MASTER = 0x2,
+ BIGEND_DIRECT_SLAVE_LOCAL0 = 0x4,
+ BIGEND_ROM = 0x8,
+ BIGEND_BYTE_LANE = 0x10, /* use byte lane consisting of most significant bits instead of least significant */
+ BIGEND_DIRECT_SLAVE_LOCAL1 = 0x20,
+ BIGEND_DMA1 = 0x40,
+ BIGEND_DMA0 = 0x80,
+};
+
+/* Note: The Expansion ROM stuff is only relevant to the PC environment.
+** This expansion ROM code is executed by the host CPU at boot time.
+** For this reason no bit definitions are provided here.
+*/
+#define PLX_ROMRNG_REG 0x0010 /* L, Expn ROM Space Range Register */
+#define PLX_ROMMAP_REG 0x0014 /* L, Local Addr Space Range Register */
+
+#define PLX_REGION0_REG 0x0018 /* L, Local Bus Region 0 Descriptor */
+#define RGN_WIDTH 0x00000002 /* Local bus width bits */
+#define RGN_8BITS 0x00000000 /* 08 bit Local Bus */
+#define RGN_16BITS 0x00000001 /* 16 bit Local Bus */
+#define RGN_32BITS 0x00000002 /* 32 bit Local Bus */
+#define RGN_MWS 0x0000003C /* Memory Access Wait States */
+#define RGN_0MWS 0x00000000
+#define RGN_1MWS 0x00000004
+#define RGN_2MWS 0x00000008
+#define RGN_3MWS 0x0000000C
+#define RGN_4MWS 0x00000010
+#define RGN_6MWS 0x00000018
+#define RGN_8MWS 0x00000020
+#define RGN_MRE 0x00000040 /* Memory Space Ready Input Enable */
+#define RGN_MBE 0x00000080 /* Memory Space Bterm Input Enable */
+#define RGN_READ_PREFETCH_DISABLE 0x00000100
+#define RGN_ROM_PREFETCH_DISABLE 0x00000200
+#define RGN_READ_PREFETCH_COUNT_ENABLE 0x00000400
+#define RGN_RWS 0x003C0000 /* Expn ROM Wait States */
+#define RGN_RRE 0x00400000 /* ROM Space Ready Input Enable */
+#define RGN_RBE 0x00800000 /* ROM Space Bterm Input Enable */
+#define RGN_MBEN 0x01000000 /* Memory Space Burst Enable */
+#define RGN_RBEN 0x04000000 /* ROM Space Burst Enable */
+#define RGN_THROT 0x08000000 /* De-assert TRDY when FIFO full */
+#define RGN_TRD 0xF0000000 /* Target Ready Delay /8 */
+
+#define PLX_REGION1_REG 0x00f8 /* L, Local Bus Region 1 Descriptor */
+
+#define PLX_DMRNG_REG 0x001C /* L, Direct Master Range Register */
+
+#define PLX_LBAPMEM_REG 0x0020 /* L, Lcl Base Addr for PCI mem space */
+
+#define PLX_LBAPIO_REG 0x0024 /* L, Lcl Base Addr for PCI I/O space */
+
+#define PLX_DMMAP_REG 0x0028 /* L, Direct Master Remap Register */
+#define DMM_MAE 0x00000001 /* Direct Mstr Memory Acc Enable */
+#define DMM_IAE 0x00000002 /* Direct Mstr I/O Acc Enable */
+#define DMM_LCK 0x00000004 /* LOCK Input Enable */
+#define DMM_PF4 0x00000008 /* Prefetch 4 Mode Enable */
+#define DMM_THROT 0x00000010 /* Assert IRDY when read FIFO full */
+#define DMM_PAF0 0x00000000 /* Programmable Almost fill level */
+#define DMM_PAF1 0x00000020 /* Programmable Almost fill level */
+#define DMM_PAF2 0x00000040 /* Programmable Almost fill level */
+#define DMM_PAF3 0x00000060 /* Programmable Almost fill level */
+#define DMM_PAF4 0x00000080 /* Programmable Almost fill level */
+#define DMM_PAF5 0x000000A0 /* Programmable Almost fill level */
+#define DMM_PAF6 0x000000C0 /* Programmable Almost fill level */
+#define DMM_PAF7 0x000000D0 /* Programmable Almost fill level */
+#define DMM_MAP 0xFFFF0000 /* Remap Address Bits */
+
+#define PLX_CAR_REG 0x002C /* L, Configuration Address Register */
+#define CAR_CT0 0x00000000 /* Config Type 0 */
+#define CAR_CT1 0x00000001 /* Config Type 1 */
+#define CAR_REG 0x000000FC /* Register Number Bits */
+#define CAR_FUN 0x00000700 /* Function Number Bits */
+#define CAR_DEV 0x0000F800 /* Device Number Bits */
+#define CAR_BUS 0x00FF0000 /* Bus Number Bits */
+#define CAR_CFG 0x80000000 /* Config Spc Access Enable */
+
+#define PLX_DBR_IN_REG 0x0060 /* L, PCI to Local Doorbell Register */
+
+#define PLX_DBR_OUT_REG 0x0064 /* L, Local to PCI Doorbell Register */
+
+#define PLX_INTRCS_REG 0x0068 /* L, Interrupt Control/Status Reg */
+#define ICS_AERR 0x00000001 /* Assert LSERR on ABORT */
+#define ICS_PERR 0x00000002 /* Assert LSERR on Parity Error */
+#define ICS_SERR 0x00000004 /* Generate PCI SERR# */
+#define ICS_MBIE 0x00000008 /* mailbox interrupt enable */
+#define ICS_PIE 0x00000100 /* PCI Interrupt Enable */
+#define ICS_PDIE 0x00000200 /* PCI Doorbell Interrupt Enable */
+#define ICS_PAIE 0x00000400 /* PCI Abort Interrupt Enable */
+#define ICS_PLIE 0x00000800 /* PCI Local Int Enable */
+#define ICS_RAE 0x00001000 /* Retry Abort Enable */
+#define ICS_PDIA 0x00002000 /* PCI Doorbell Interrupt Active */
+#define ICS_PAIA 0x00004000 /* PCI Abort Interrupt Active */
+#define ICS_LIA 0x00008000 /* Local Interrupt Active */
+#define ICS_LIE 0x00010000 /* Local Interrupt Enable */
+#define ICS_LDIE 0x00020000 /* Local Doorbell Int Enable */
+#define ICS_DMA0_E 0x00040000 /* DMA #0 Interrupt Enable */
+#define ICS_DMA1_E 0x00080000 /* DMA #1 Interrupt Enable */
+#define ICS_LDIA 0x00100000 /* Local Doorbell Int Active */
+#define ICS_DMA0_A 0x00200000 /* DMA #0 Interrupt Active */
+#define ICS_DMA1_A 0x00400000 /* DMA #1 Interrupt Active */
+#define ICS_BIA 0x00800000 /* BIST Interrupt Active */
+#define ICS_TA_DM 0x01000000 /* Target Abort - Direct Master */
+#define ICS_TA_DMA0 0x02000000 /* Target Abort - DMA #0 */
+#define ICS_TA_DMA1 0x04000000 /* Target Abort - DMA #1 */
+#define ICS_TA_RA 0x08000000 /* Target Abort - Retry Timeout */
+#define ICS_MBIA(x) (0x10000000 << ((x) & 0x3)) /* mailbox x is active */
+
+#define PLX_CONTROL_REG 0x006C /* L, EEPROM Cntl & PCI Cmd Codes */
+#define CTL_RDMA 0x0000000E /* DMA Read Command */
+#define CTL_WDMA 0x00000070 /* DMA Write Command */
+#define CTL_RMEM 0x00000600 /* Memory Read Command */
+#define CTL_WMEM 0x00007000 /* Memory Write Command */
+#define CTL_USERO 0x00010000 /* USERO output pin control bit */
+#define CTL_USERI 0x00020000 /* USERI input pin bit */
+#define CTL_EE_CLK 0x01000000 /* EEPROM Clock line */
+#define CTL_EE_CS 0x02000000 /* EEPROM Chip Select */
+#define CTL_EE_W 0x04000000 /* EEPROM Write bit */
+#define CTL_EE_R 0x08000000 /* EEPROM Read bit */
+#define CTL_EECHK 0x10000000 /* EEPROM Present bit */
+#define CTL_EERLD 0x20000000 /* EEPROM Reload Register */
+#define CTL_RESET 0x40000000 /* !! Adapter Reset !! */
+#define CTL_READY 0x80000000 /* Local Init Done */
+
+#define PLX_ID_REG 0x70 /* hard-coded plx vendor and device ids */
+
+#define PLX_REVISION_REG 0x74 /* silicon revision */
+
+#define PLX_DMA0_MODE_REG 0x80 /* dma channel 0 mode register */
+#define PLX_DMA1_MODE_REG 0x94 /* dma channel 0 mode register */
+#define PLX_LOCAL_BUS_16_WIDE_BITS 0x1
+#define PLX_LOCAL_BUS_32_WIDE_BITS 0x3
+#define PLX_LOCAL_BUS_WIDTH_MASK 0x3
+#define PLX_DMA_EN_READYIN_BIT 0x40 /* enable ready in input */
+#define PLX_EN_BTERM_BIT 0x80 /* enable BTERM# input */
+#define PLX_DMA_LOCAL_BURST_EN_BIT 0x100 /* enable local burst mode */
+#define PLX_EN_CHAIN_BIT 0x200 /* enables chaining */
+#define PLX_EN_DMA_DONE_INTR_BIT 0x400 /* enables interrupt on dma done */
+#define PLX_LOCAL_ADDR_CONST_BIT 0x800 /* hold local address constant (don't increment) */
+#define PLX_DEMAND_MODE_BIT 0x1000 /* enables demand-mode for dma transfer */
+#define PLX_EOT_ENABLE_BIT 0x4000
+#define PLX_STOP_MODE_BIT 0x8000
+#define PLX_DMA_INTR_PCI_BIT 0x20000 /* routes dma interrupt to pci bus (instead of local bus) */
+
+#define PLX_DMA0_PCI_ADDRESS_REG 0x84 /* pci address that dma transfers start at */
+#define PLX_DMA1_PCI_ADDRESS_REG 0x98
+
+#define PLX_DMA0_LOCAL_ADDRESS_REG 0x88 /* local address that dma transfers start at */
+#define PLX_DMA1_LOCAL_ADDRESS_REG 0x9c
+
+#define PLX_DMA0_TRANSFER_SIZE_REG 0x8c /* number of bytes to transfer (first 23 bits) */
+#define PLX_DMA1_TRANSFER_SIZE_REG 0xa0
+
+#define PLX_DMA0_DESCRIPTOR_REG 0x90 /* descriptor pointer register */
+#define PLX_DMA1_DESCRIPTOR_REG 0xa4
+#define PLX_DESC_IN_PCI_BIT 0x1 /* descriptor is located in pci space (not local space) */
+#define PLX_END_OF_CHAIN_BIT 0x2 /* end of chain bit */
+#define PLX_INTR_TERM_COUNT 0x4 /* interrupt when this descriptor's transfer is finished */
+#define PLX_XFER_LOCAL_TO_PCI 0x8 /* transfer from local to pci bus (not pci to local) */
+
+#define PLX_DMA0_CS_REG 0xa8 /* command status register */
+#define PLX_DMA1_CS_REG 0xa9
+#define PLX_DMA_EN_BIT 0x1 /* enable dma channel */
+#define PLX_DMA_START_BIT 0x2 /* start dma transfer */
+#define PLX_DMA_ABORT_BIT 0x4 /* abort dma transfer */
+#define PLX_CLEAR_DMA_INTR_BIT 0x8 /* clear dma interrupt */
+#define PLX_DMA_DONE_BIT 0x10 /* transfer done status bit */
+
+#define PLX_DMA0_THRESHOLD_REG 0xb0 /* command status register */
+
+/*
+ * Accesses near the end of memory can cause the PLX chip
+ * to pre-fetch data off of end-of-ram. Limit the size of
+ * memory so host-side accesses cannot occur.
+ */
+
+#define PLX_PREFETCH 32
+
+/*
+ * The PCI Interface, via the PCI-9060 Chip, has up to eight (8) Mailbox
+ * Registers. The PUTS (Power-Up Test Suite) handles the board-side
+ * interface/interaction using the first 4 registers. Specifications for
+ * the use of the full PUTS' command and status interface is contained
+ * within a separate SBE PUTS Manual. The Host-Side Device Driver only
+ * uses a subset of the full PUTS interface.
+ */
+
+/*****************************************/
+/*** MAILBOX #(-1) - MEM ACCESS STS ***/
+/*****************************************/
+
+#define MBX_STS_VALID 0x57584744 /* 'WXGD' */
+#define MBX_STS_DILAV 0x44475857 /* swapped = 'DGXW' */
+
+/*****************************************/
+/*** MAILBOX #0 - PUTS STATUS ***/
+/*****************************************/
+
+#define MBX_STS_MASK 0x000000ff /* PUTS Status Register bits */
+#define MBX_STS_TMASK 0x0000000f /* register bits for TEST number */
+
+#define MBX_STS_PCIRESET 0x00000100 /* Host issued PCI reset request */
+#define MBX_STS_BUSY 0x00000080 /* PUTS is in progress */
+#define MBX_STS_ERROR 0x00000040 /* PUTS has failed */
+#define MBX_STS_RESERVED 0x000000c0 /* Undefined -> status in transition.
+ We are in process of changing
+ bits; we SET Error bit before
+ RESET of Busy bit */
+
+#define MBX_RESERVED_5 0x00000020 /* FYI: reserved/unused bit */
+#define MBX_RESERVED_4 0x00000010 /* FYI: reserved/unused bit */
+
+/******************************************/
+/*** MAILBOX #1 - PUTS COMMANDS ***/
+/******************************************/
+
+/*
+ * Any attempt to execute an unimplement command results in the PUTS
+ * interface executing a NOOP and continuing as if the offending command
+ * completed normally. Note: this supplies a simple method to interrogate
+ * mailbox command processing functionality.
+ */
+
+#define MBX_CMD_MASK 0xffff0000 /* PUTS Command Register bits */
+
+#define MBX_CMD_ABORTJ 0x85000000 /* abort and jump */
+#define MBX_CMD_RESETP 0x86000000 /* reset and pause at start */
+#define MBX_CMD_PAUSE 0x87000000 /* pause immediately */
+#define MBX_CMD_PAUSEC 0x88000000 /* pause on completion */
+#define MBX_CMD_RESUME 0x89000000 /* resume operation */
+#define MBX_CMD_STEP 0x8a000000 /* single step tests */
+
+#define MBX_CMD_BSWAP 0x8c000000 /* identify byte swap scheme */
+#define MBX_CMD_BSWAP_0 0x8c000000 /* use scheme 0 */
+#define MBX_CMD_BSWAP_1 0x8c000001 /* use scheme 1 */
+
+#define MBX_CMD_SETHMS 0x8d000000 /* setup host memory access window
+ size */
+#define MBX_CMD_SETHBA 0x8e000000 /* setup host memory access base
+ address */
+#define MBX_CMD_MGO 0x8f000000 /* perform memory setup and continue
+ (IE. Done) */
+#define MBX_CMD_NOOP 0xFF000000 /* dummy, illegal command */
+
+/*****************************************/
+/*** MAILBOX #2 - MEMORY SIZE ***/
+/*****************************************/
+
+#define MBX_MEMSZ_MASK 0xffff0000 /* PUTS Memory Size Register bits */
+
+#define MBX_MEMSZ_128KB 0x00020000 /* 128 kilobyte board */
+#define MBX_MEMSZ_256KB 0x00040000 /* 256 kilobyte board */
+#define MBX_MEMSZ_512KB 0x00080000 /* 512 kilobyte board */
+#define MBX_MEMSZ_1MB 0x00100000 /* 1 megabyte board */
+#define MBX_MEMSZ_2MB 0x00200000 /* 2 megabyte board */
+#define MBX_MEMSZ_4MB 0x00400000 /* 4 megabyte board */
+#define MBX_MEMSZ_8MB 0x00800000 /* 8 megabyte board */
+#define MBX_MEMSZ_16MB 0x01000000 /* 16 megabyte board */
+
+/***************************************/
+/*** MAILBOX #2 - BOARD TYPE ***/
+/***************************************/
+
+#define MBX_BTYPE_MASK 0x0000ffff /* PUTS Board Type Register */
+#define MBX_BTYPE_FAMILY_MASK 0x0000ff00 /* PUTS Board Family Register */
+#define MBX_BTYPE_SUBTYPE_MASK 0x000000ff /* PUTS Board Subtype */
+
+#define MBX_BTYPE_PLX9060 0x00000100 /* PLX family type */
+#define MBX_BTYPE_PLX9080 0x00000300 /* PLX wanXL100s family type */
+
+#define MBX_BTYPE_WANXL_4 0x00000104 /* wanXL400, 4-port */
+#define MBX_BTYPE_WANXL_2 0x00000102 /* wanXL200, 2-port */
+#define MBX_BTYPE_WANXL_1s 0x00000301 /* wanXL100s, 1-port */
+#define MBX_BTYPE_WANXL_1t 0x00000401 /* wanXL100T1, 1-port */
+
+/*****************************************/
+/*** MAILBOX #3 - SHMQ MAILBOX ***/
+/*****************************************/
+
+#define MBX_SMBX_MASK 0x000000ff /* PUTS SHMQ Mailbox bits */
+
+/***************************************/
+/*** GENERIC HOST-SIDE DRIVER ***/
+/***************************************/
+
+#define MBX_ERR 0
+#define MBX_OK 1
+
+/* mailbox check routine - type of testing */
+#define MBXCHK_STS 0x00 /* check for PUTS status */
+#define MBXCHK_NOWAIT 0x01 /* dont care about PUTS status */
+
+/* system allocates this many bytes for address mapping mailbox space */
+#define MBX_ADDR_SPACE_360 0x80 /* wanXL100s/200/400 */
+#define MBX_ADDR_MASK_360 (MBX_ADDR_SPACE_360-1)
+
+static inline int plx9080_abort_dma(void __iomem *iobase, unsigned int channel)
+{
+ void __iomem *dma_cs_addr;
+ uint8_t dma_status;
+ const int timeout = 10000;
+ unsigned int i;
+
+ if (channel)
+ dma_cs_addr = iobase + PLX_DMA1_CS_REG;
+ else
+ dma_cs_addr = iobase + PLX_DMA0_CS_REG;
+
+ /* abort dma transfer if necessary */
+ dma_status = readb(dma_cs_addr);
+ if ((dma_status & PLX_DMA_EN_BIT) == 0)
+ return 0;
+
+ /* wait to make sure done bit is zero */
+ for (i = 0; (dma_status & PLX_DMA_DONE_BIT) && i < timeout; i++) {
+ udelay(1);
+ dma_status = readb(dma_cs_addr);
+ }
+ if (i == timeout)
+ return -ETIMEDOUT;
+
+ /* disable and abort channel */
+ writeb(PLX_DMA_ABORT_BIT, dma_cs_addr);
+ /* wait for dma done bit */
+ dma_status = readb(dma_cs_addr);
+ for (i = 0; (dma_status & PLX_DMA_DONE_BIT) == 0 && i < timeout; i++) {
+ udelay(1);
+ dma_status = readb(dma_cs_addr);
+ }
+ if (i == timeout)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+#endif /* __COMEDI_PLX9080_H */
diff --git a/drivers/staging/comedi/drivers/quatech_daqp_cs.c b/drivers/staging/comedi/drivers/quatech_daqp_cs.c
new file mode 100644
index 000000000..152cb146f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/quatech_daqp_cs.c
@@ -0,0 +1,815 @@
+/*======================================================================
+
+ comedi/drivers/quatech_daqp_cs.c
+
+ Quatech DAQP PCMCIA data capture cards COMEDI client driver
+ Copyright (C) 2000, 2003 Brent Baccala <baccala@freesoft.org>
+ The DAQP interface code in this file is released into the public domain.
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ http://www.comedi.org/
+
+ quatech_daqp_cs.c 1.10
+
+ Documentation for the DAQP PCMCIA cards can be found on Quatech's site:
+
+ ftp://ftp.quatech.com/Manuals/daqp-208.pdf
+
+ This manual is for both the DAQP-208 and the DAQP-308.
+
+ What works:
+
+ - A/D conversion
+ - 8 channels
+ - 4 gain ranges
+ - ground ref or differential
+ - single-shot and timed both supported
+ - D/A conversion, single-shot
+ - digital I/O
+
+ What doesn't:
+
+ - any kind of triggering - external or D/A channel 1
+ - the card's optional expansion board
+ - the card's timer (for anything other than A/D conversion)
+ - D/A update modes other than immediate (i.e, timed)
+ - fancier timing modes
+ - setting card's FIFO buffer thresholds to anything but default
+
+======================================================================*/
+
+/*
+Driver: quatech_daqp_cs
+Description: Quatech DAQP PCMCIA data capture cards
+Author: Brent Baccala <baccala@freesoft.org>
+Status: works
+Devices: [Quatech] DAQP-208 (daqp), DAQP-308
+*/
+
+#include <linux/module.h>
+#include <linux/semaphore.h>
+#include <linux/completion.h>
+
+#include "../comedi_pcmcia.h"
+
+struct daqp_private {
+ int stop;
+
+ enum { semaphore, buffer } interrupt_mode;
+
+ struct completion eos;
+};
+
+/* The DAQP communicates with the system through a 16 byte I/O window. */
+
+#define DAQP_FIFO_SIZE 4096
+
+#define DAQP_FIFO 0
+#define DAQP_SCANLIST 1
+#define DAQP_CONTROL 2
+#define DAQP_STATUS 2
+#define DAQP_DIGITAL_IO 3
+#define DAQP_PACER_LOW 4
+#define DAQP_PACER_MID 5
+#define DAQP_PACER_HIGH 6
+#define DAQP_COMMAND 7
+#define DAQP_DA 8
+#define DAQP_TIMER 10
+#define DAQP_AUX 15
+
+#define DAQP_SCANLIST_DIFFERENTIAL 0x4000
+#define DAQP_SCANLIST_GAIN(x) ((x)<<12)
+#define DAQP_SCANLIST_CHANNEL(x) ((x)<<8)
+#define DAQP_SCANLIST_START 0x0080
+#define DAQP_SCANLIST_EXT_GAIN(x) ((x)<<4)
+#define DAQP_SCANLIST_EXT_CHANNEL(x) (x)
+
+#define DAQP_CONTROL_PACER_100kHz 0xc0
+#define DAQP_CONTROL_PACER_1MHz 0x80
+#define DAQP_CONTROL_PACER_5MHz 0x40
+#define DAQP_CONTROL_PACER_EXTERNAL 0x00
+#define DAQP_CONTORL_EXPANSION 0x20
+#define DAQP_CONTROL_EOS_INT_ENABLE 0x10
+#define DAQP_CONTROL_FIFO_INT_ENABLE 0x08
+#define DAQP_CONTROL_TRIGGER_ONESHOT 0x00
+#define DAQP_CONTROL_TRIGGER_CONTINUOUS 0x04
+#define DAQP_CONTROL_TRIGGER_INTERNAL 0x00
+#define DAQP_CONTROL_TRIGGER_EXTERNAL 0x02
+#define DAQP_CONTROL_TRIGGER_RISING 0x00
+#define DAQP_CONTROL_TRIGGER_FALLING 0x01
+
+#define DAQP_STATUS_IDLE 0x80
+#define DAQP_STATUS_RUNNING 0x40
+#define DAQP_STATUS_EVENTS 0x38
+#define DAQP_STATUS_DATA_LOST 0x20
+#define DAQP_STATUS_END_OF_SCAN 0x10
+#define DAQP_STATUS_FIFO_THRESHOLD 0x08
+#define DAQP_STATUS_FIFO_FULL 0x04
+#define DAQP_STATUS_FIFO_NEARFULL 0x02
+#define DAQP_STATUS_FIFO_EMPTY 0x01
+
+#define DAQP_COMMAND_ARM 0x80
+#define DAQP_COMMAND_RSTF 0x40
+#define DAQP_COMMAND_RSTQ 0x20
+#define DAQP_COMMAND_STOP 0x10
+#define DAQP_COMMAND_LATCH 0x08
+#define DAQP_COMMAND_100kHz 0x00
+#define DAQP_COMMAND_50kHz 0x02
+#define DAQP_COMMAND_25kHz 0x04
+#define DAQP_COMMAND_FIFO_DATA 0x01
+#define DAQP_COMMAND_FIFO_PROGRAM 0x00
+
+#define DAQP_AUX_TRIGGER_TTL 0x00
+#define DAQP_AUX_TRIGGER_ANALOG 0x80
+#define DAQP_AUX_TRIGGER_PRETRIGGER 0x40
+#define DAQP_AUX_TIMER_INT_ENABLE 0x20
+#define DAQP_AUX_TIMER_RELOAD 0x00
+#define DAQP_AUX_TIMER_PAUSE 0x08
+#define DAQP_AUX_TIMER_GO 0x10
+#define DAQP_AUX_TIMER_GO_EXTERNAL 0x18
+#define DAQP_AUX_TIMER_EXTERNAL_SRC 0x04
+#define DAQP_AUX_TIMER_INTERNAL_SRC 0x00
+#define DAQP_AUX_DA_DIRECT 0x00
+#define DAQP_AUX_DA_OVERFLOW 0x01
+#define DAQP_AUX_DA_EXTERNAL 0x02
+#define DAQP_AUX_DA_PACER 0x03
+
+#define DAQP_AUX_RUNNING 0x80
+#define DAQP_AUX_TRIGGERED 0x40
+#define DAQP_AUX_DA_BUFFER 0x20
+#define DAQP_AUX_TIMER_OVERFLOW 0x10
+#define DAQP_AUX_CONVERSION 0x08
+#define DAQP_AUX_DATA_LOST 0x04
+#define DAQP_AUX_FIFO_NEARFULL 0x02
+#define DAQP_AUX_FIFO_EMPTY 0x01
+
+static const struct comedi_lrange range_daqp_ai = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+/* Cancel a running acquisition */
+
+static int daqp_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct daqp_private *devpriv = dev->private;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ outb(DAQP_COMMAND_STOP, dev->iobase + DAQP_COMMAND);
+
+ /* flush any linguring data in FIFO - superfluous here */
+ /* outb(DAQP_COMMAND_RSTF, dev->iobase+DAQP_COMMAND); */
+
+ devpriv->interrupt_mode = semaphore;
+
+ return 0;
+}
+
+/* Interrupt handler
+ *
+ * Operates in one of two modes. If devpriv->interrupt_mode is
+ * 'semaphore', just signal the devpriv->eos completion and return
+ * (one-shot mode). Otherwise (continuous mode), read data in from
+ * the card, transfer it to the buffer provided by the higher-level
+ * comedi kernel module, and signal various comedi callback routines,
+ * which run pretty quick.
+ */
+static enum irqreturn daqp_interrupt(int irq, void *dev_id)
+{
+ struct comedi_device *dev = dev_id;
+ struct daqp_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int loop_limit = 10000;
+ int status;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ switch (devpriv->interrupt_mode) {
+ case semaphore:
+ complete(&devpriv->eos);
+ break;
+
+ case buffer:
+ while (!((status = inb(dev->iobase + DAQP_STATUS))
+ & DAQP_STATUS_FIFO_EMPTY)) {
+ unsigned short data;
+
+ if (status & DAQP_STATUS_DATA_LOST) {
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ dev_warn(dev->class_dev, "data lost\n");
+ break;
+ }
+
+ data = inb(dev->iobase + DAQP_FIFO);
+ data |= inb(dev->iobase + DAQP_FIFO) << 8;
+ data ^= 0x8000;
+
+ comedi_buf_write_samples(s, &data, 1);
+
+ /* If there's a limit, decrement it
+ * and stop conversion if zero
+ */
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ s->async->scans_done >= cmd->stop_arg) {
+ s->async->events |= COMEDI_CB_EOA;
+ break;
+ }
+
+ if ((loop_limit--) <= 0)
+ break;
+ }
+
+ if (loop_limit <= 0) {
+ dev_warn(dev->class_dev,
+ "loop_limit reached in daqp_interrupt()\n");
+ s->async->events |= COMEDI_CB_ERROR;
+ }
+
+ comedi_handle_events(dev, s);
+ }
+ return IRQ_HANDLED;
+}
+
+static void daqp_ai_set_one_scanlist_entry(struct comedi_device *dev,
+ unsigned int chanspec,
+ int start)
+{
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned int val;
+
+ val = DAQP_SCANLIST_CHANNEL(chan) | DAQP_SCANLIST_GAIN(range);
+
+ if (aref == AREF_DIFF)
+ val |= DAQP_SCANLIST_DIFFERENTIAL;
+
+ if (start)
+ val |= DAQP_SCANLIST_START;
+
+ outb(val & 0xff, dev->iobase + DAQP_SCANLIST);
+ outb((val >> 8) & 0xff, dev->iobase + DAQP_SCANLIST);
+}
+
+/* One-shot analog data acquisition routine */
+
+static int daqp_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct daqp_private *devpriv = dev->private;
+ int i;
+ int v;
+ int counter = 10000;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ /* Stop any running conversion */
+ daqp_ai_cancel(dev, s);
+
+ outb(0, dev->iobase + DAQP_AUX);
+
+ /* Reset scan list queue */
+ outb(DAQP_COMMAND_RSTQ, dev->iobase + DAQP_COMMAND);
+
+ /* Program one scan list entry */
+ daqp_ai_set_one_scanlist_entry(dev, insn->chanspec, 1);
+
+ /* Reset data FIFO (see page 28 of DAQP User's Manual) */
+
+ outb(DAQP_COMMAND_RSTF, dev->iobase + DAQP_COMMAND);
+
+ /* Set trigger */
+
+ v = DAQP_CONTROL_TRIGGER_ONESHOT | DAQP_CONTROL_TRIGGER_INTERNAL
+ | DAQP_CONTROL_PACER_100kHz | DAQP_CONTROL_EOS_INT_ENABLE;
+
+ outb(v, dev->iobase + DAQP_CONTROL);
+
+ /* Reset any pending interrupts (my card has a tendency to require
+ * require multiple reads on the status register to achieve this)
+ */
+
+ while (--counter
+ && (inb(dev->iobase + DAQP_STATUS) & DAQP_STATUS_EVENTS))
+ ;
+ if (!counter) {
+ dev_err(dev->class_dev,
+ "couldn't clear interrupts in status register\n");
+ return -1;
+ }
+
+ init_completion(&devpriv->eos);
+ devpriv->interrupt_mode = semaphore;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Start conversion */
+ outb(DAQP_COMMAND_ARM | DAQP_COMMAND_FIFO_DATA,
+ dev->iobase + DAQP_COMMAND);
+
+ /* Wait for interrupt service routine to unblock completion */
+ /* Maybe could use a timeout here, but it's interruptible */
+ if (wait_for_completion_interruptible(&devpriv->eos))
+ return -EINTR;
+
+ data[i] = inb(dev->iobase + DAQP_FIFO);
+ data[i] |= inb(dev->iobase + DAQP_FIFO) << 8;
+ data[i] ^= 0x8000;
+ }
+
+ return insn->n;
+}
+
+/* This function converts ns nanoseconds to a counter value suitable
+ * for programming the device. We always use the DAQP's 5 MHz clock,
+ * which with its 24-bit counter, allows values up to 84 seconds.
+ * Also, the function adjusts ns so that it cooresponds to the actual
+ * time that the device will use.
+ */
+
+static int daqp_ns_to_timer(unsigned int *ns, unsigned int flags)
+{
+ int timer;
+
+ timer = *ns / 200;
+ *ns = timer * 200;
+
+ return timer;
+}
+
+/* cmdtest tests a particular command to see if it is valid.
+ * Using the cmdtest ioctl, a user can create a valid cmd
+ * and then have it executed by the cmd ioctl.
+ *
+ * cmdtest returns 1,2,3,4 or 0, depending on which tests
+ * the command passes.
+ */
+
+static int daqp_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SPEED 10000 /* 100 kHz - in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED);
+ }
+
+ /* If both scan_begin and convert are both timer values, the only
+ * way that can make sense is if the scan time is the number of
+ * conversions times the convert time
+ */
+
+ if (cmd->scan_begin_src == TRIG_TIMER && cmd->convert_src == TRIG_TIMER
+ && cmd->scan_begin_arg != cmd->convert_arg * cmd->scan_end_arg) {
+ err |= -EINVAL;
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ MAX_SPEED);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ daqp_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ daqp_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int daqp_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct daqp_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int counter;
+ int scanlist_start_on_every_entry;
+ int threshold;
+
+ int i;
+ int v;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ /* Stop any running conversion */
+ daqp_ai_cancel(dev, s);
+
+ outb(0, dev->iobase + DAQP_AUX);
+
+ /* Reset scan list queue */
+ outb(DAQP_COMMAND_RSTQ, dev->iobase + DAQP_COMMAND);
+
+ /* Program pacer clock
+ *
+ * There's two modes we can operate in. If convert_src is
+ * TRIG_TIMER, then convert_arg specifies the time between
+ * each conversion, so we program the pacer clock to that
+ * frequency and set the SCANLIST_START bit on every scanlist
+ * entry. Otherwise, convert_src is TRIG_NOW, which means
+ * we want the fastest possible conversions, scan_begin_src
+ * is TRIG_TIMER, and scan_begin_arg specifies the time between
+ * each scan, so we program the pacer clock to this frequency
+ * and only set the SCANLIST_START bit on the first entry.
+ */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ counter = daqp_ns_to_timer(&cmd->convert_arg, cmd->flags);
+ outb(counter & 0xff, dev->iobase + DAQP_PACER_LOW);
+ outb((counter >> 8) & 0xff, dev->iobase + DAQP_PACER_MID);
+ outb((counter >> 16) & 0xff, dev->iobase + DAQP_PACER_HIGH);
+ scanlist_start_on_every_entry = 1;
+ } else {
+ counter = daqp_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
+ outb(counter & 0xff, dev->iobase + DAQP_PACER_LOW);
+ outb((counter >> 8) & 0xff, dev->iobase + DAQP_PACER_MID);
+ outb((counter >> 16) & 0xff, dev->iobase + DAQP_PACER_HIGH);
+ scanlist_start_on_every_entry = 0;
+ }
+
+ /* Program scan list */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ int start = (i == 0 || scanlist_start_on_every_entry);
+
+ daqp_ai_set_one_scanlist_entry(dev, cmd->chanlist[i], start);
+ }
+
+ /* Now it's time to program the FIFO threshold, basically the
+ * number of samples the card will buffer before it interrupts
+ * the CPU.
+ *
+ * If we don't have a stop count, then use half the size of
+ * the FIFO (the manufacturer's recommendation). Consider
+ * that the FIFO can hold 2K samples (4K bytes). With the
+ * threshold set at half the FIFO size, we have a margin of
+ * error of 1024 samples. At the chip's maximum sample rate
+ * of 100,000 Hz, the CPU would have to delay interrupt
+ * service for a full 10 milliseconds in order to lose data
+ * here (as opposed to higher up in the kernel). I've never
+ * seen it happen. However, for slow sample rates it may
+ * buffer too much data and introduce too much delay for the
+ * user application.
+ *
+ * If we have a stop count, then things get more interesting.
+ * If the stop count is less than the FIFO size (actually
+ * three-quarters of the FIFO size - see below), we just use
+ * the stop count itself as the threshold, the card interrupts
+ * us when that many samples have been taken, and we kill the
+ * acquisition at that point and are done. If the stop count
+ * is larger than that, then we divide it by 2 until it's less
+ * than three quarters of the FIFO size (we always leave the
+ * top quarter of the FIFO as protection against sluggish CPU
+ * interrupt response) and use that as the threshold. So, if
+ * the stop count is 4000 samples, we divide by two twice to
+ * get 1000 samples, use that as the threshold, take four
+ * interrupts to get our 4000 samples and are done.
+ *
+ * The algorithm could be more clever. For example, if 81000
+ * samples are requested, we could set the threshold to 1500
+ * samples and take 54 interrupts to get 81000. But 54 isn't
+ * a power of two, so this algorithm won't find that option.
+ * Instead, it'll set the threshold at 1266 and take 64
+ * interrupts to get 81024 samples, of which the last 24 will
+ * be discarded... but we won't get the last interrupt until
+ * they've been collected. To find the first option, the
+ * computer could look at the prime decomposition of the
+ * sample count (81000 = 3^4 * 5^3 * 2^3) and factor it into a
+ * threshold (1500 = 3 * 5^3 * 2^2) and an interrupt count (54
+ * = 3^3 * 2). Hmmm... a one-line while loop or prime
+ * decomposition of integers... I'll leave it the way it is.
+ *
+ * I'll also note a mini-race condition before ignoring it in
+ * the code. Let's say we're taking 4000 samples, as before.
+ * After 1000 samples, we get an interrupt. But before that
+ * interrupt is completely serviced, another sample is taken
+ * and loaded into the FIFO. Since the interrupt handler
+ * empties the FIFO before returning, it will read 1001 samples.
+ * If that happens four times, we'll end up taking 4004 samples,
+ * not 4000. The interrupt handler will discard the extra four
+ * samples (by halting the acquisition with four samples still
+ * in the FIFO), but we will have to wait for them.
+ *
+ * In short, this code works pretty well, but for either of
+ * the two reasons noted, might end up waiting for a few more
+ * samples than actually requested. Shouldn't make too much
+ * of a difference.
+ */
+
+ /* Save away the number of conversions we should perform, and
+ * compute the FIFO threshold (in bytes, not samples - that's
+ * why we multiple devpriv->count by 2 = sizeof(sample))
+ */
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ unsigned long long nsamples;
+ unsigned long long nbytes;
+
+ nsamples = (unsigned long long)cmd->stop_arg *
+ cmd->scan_end_arg;
+ nbytes = nsamples * comedi_bytes_per_sample(s);
+ while (nbytes > DAQP_FIFO_SIZE * 3 / 4)
+ nbytes /= 2;
+ threshold = nbytes;
+ } else {
+ threshold = DAQP_FIFO_SIZE / 2;
+ }
+
+ /* Reset data FIFO (see page 28 of DAQP User's Manual) */
+
+ outb(DAQP_COMMAND_RSTF, dev->iobase + DAQP_COMMAND);
+
+ /* Set FIFO threshold. First two bytes are near-empty
+ * threshold, which is unused; next two bytes are near-full
+ * threshold. We computed the number of bytes we want in the
+ * FIFO when the interrupt is generated, what the card wants
+ * is actually the number of available bytes left in the FIFO
+ * when the interrupt is to happen.
+ */
+
+ outb(0x00, dev->iobase + DAQP_FIFO);
+ outb(0x00, dev->iobase + DAQP_FIFO);
+
+ outb((DAQP_FIFO_SIZE - threshold) & 0xff, dev->iobase + DAQP_FIFO);
+ outb((DAQP_FIFO_SIZE - threshold) >> 8, dev->iobase + DAQP_FIFO);
+
+ /* Set trigger */
+
+ v = DAQP_CONTROL_TRIGGER_CONTINUOUS | DAQP_CONTROL_TRIGGER_INTERNAL
+ | DAQP_CONTROL_PACER_5MHz | DAQP_CONTROL_FIFO_INT_ENABLE;
+
+ outb(v, dev->iobase + DAQP_CONTROL);
+
+ /* Reset any pending interrupts (my card has a tendency to require
+ * require multiple reads on the status register to achieve this)
+ */
+ counter = 100;
+ while (--counter
+ && (inb(dev->iobase + DAQP_STATUS) & DAQP_STATUS_EVENTS))
+ ;
+ if (!counter) {
+ dev_err(dev->class_dev,
+ "couldn't clear interrupts in status register\n");
+ return -1;
+ }
+
+ devpriv->interrupt_mode = buffer;
+
+ /* Start conversion */
+ outb(DAQP_COMMAND_ARM | DAQP_COMMAND_FIFO_DATA,
+ dev->iobase + DAQP_COMMAND);
+
+ return 0;
+}
+
+static int daqp_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqp_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ /* Make sure D/A update mode is direct update */
+ outb(0, dev->iobase + DAQP_AUX);
+
+ for (i = 0; i > insn->n; i++) {
+ unsigned val = data[i];
+
+ s->readback[chan] = val;
+
+ val &= 0x0fff;
+ val ^= 0x0800; /* Flip the sign */
+ val |= (chan << 12);
+
+ outw(val, dev->iobase + DAQP_DA);
+ }
+
+ return insn->n;
+}
+
+static int daqp_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqp_private *devpriv = dev->private;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ data[0] = inb(dev->iobase + DAQP_DIGITAL_IO);
+
+ return insn->n;
+}
+
+static int daqp_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqp_private *devpriv = dev->private;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAQP_DIGITAL_IO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int daqp_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct daqp_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ link->priv = dev;
+ ret = pcmcia_request_irq(link, daqp_interrupt);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ;
+ s->n_chan = 8;
+ s->len_chanlist = 2048;
+ s->maxdata = 0xffff;
+ s->range_table = &range_daqp_ai;
+ s->insn_read = daqp_ai_insn_read;
+ s->do_cmdtest = daqp_ai_cmdtest;
+ s->do_cmd = daqp_ai_cmd;
+ s->cancel = daqp_ai_cancel;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_bipolar5;
+ s->insn_write = daqp_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->insn_bits = daqp_di_insn_bits;
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->insn_bits = daqp_do_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver driver_daqp = {
+ .driver_name = "quatech_daqp_cs",
+ .module = THIS_MODULE,
+ .auto_attach = daqp_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int daqp_cs_suspend(struct pcmcia_device *link)
+{
+ struct comedi_device *dev = link->priv;
+ struct daqp_private *devpriv = dev ? dev->private : NULL;
+
+ /* Mark the device as stopped, to block IO until later */
+ if (devpriv)
+ devpriv->stop = 1;
+
+ return 0;
+}
+
+static int daqp_cs_resume(struct pcmcia_device *link)
+{
+ struct comedi_device *dev = link->priv;
+ struct daqp_private *devpriv = dev ? dev->private : NULL;
+
+ if (devpriv)
+ devpriv->stop = 0;
+
+ return 0;
+}
+
+static int daqp_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_daqp);
+}
+
+static const struct pcmcia_device_id daqp_cs_id_table[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x0137, 0x0027),
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, daqp_cs_id_table);
+
+static struct pcmcia_driver daqp_cs_driver = {
+ .name = "quatech_daqp_cs",
+ .owner = THIS_MODULE,
+ .id_table = daqp_cs_id_table,
+ .probe = daqp_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+ .suspend = daqp_cs_suspend,
+ .resume = daqp_cs_resume,
+};
+module_comedi_pcmcia_driver(driver_daqp, daqp_cs_driver);
+
+MODULE_DESCRIPTION("Comedi driver for Quatech DAQP PCMCIA data capture cards");
+MODULE_AUTHOR("Brent Baccala <baccala@freesoft.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/rtd520.c b/drivers/staging/comedi/drivers/rtd520.c
new file mode 100644
index 000000000..4c13f5eb0
--- /dev/null
+++ b/drivers/staging/comedi/drivers/rtd520.c
@@ -0,0 +1,1344 @@
+/*
+ * comedi/drivers/rtd520.c
+ * Comedi driver for Real Time Devices (RTD) PCI4520/DM7520
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2001 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: rtd520
+ * Description: Real Time Devices PCI4520/DM7520
+ * Devices: [Real Time Devices] DM7520HR-1 (DM7520), DM7520HR-8,
+ * PCI4520 (PCI4520), PCI4520-8
+ * Author: Dan Christian
+ * Status: Works. Only tested on DM7520-8. Not SMP safe.
+ *
+ * Configuration options: not applicable, uses PCI auto config
+ */
+
+/*
+ * Created by Dan Christian, NASA Ames Research Center.
+ *
+ * The PCI4520 is a PCI card. The DM7520 is a PC/104-plus card.
+ * Both have:
+ * 8/16 12 bit ADC with FIFO and channel gain table
+ * 8 bits high speed digital out (for external MUX) (or 8 in or 8 out)
+ * 8 bits high speed digital in with FIFO and interrupt on change (or 8 IO)
+ * 2 12 bit DACs with FIFOs
+ * 2 bits output
+ * 2 bits input
+ * bus mastering DMA
+ * timers: ADC sample, pacer, burst, about, delay, DA1, DA2
+ * sample counter
+ * 3 user timer/counters (8254)
+ * external interrupt
+ *
+ * The DM7520 has slightly fewer features (fewer gain steps).
+ *
+ * These boards can support external multiplexors and multi-board
+ * synchronization, but this driver doesn't support that.
+ *
+ * Board docs: http://www.rtdusa.com/PC104/DM/analog%20IO/dm7520.htm
+ * Data sheet: http://www.rtdusa.com/pdf/dm7520.pdf
+ * Example source: http://www.rtdusa.com/examples/dm/dm7520.zip
+ * Call them and ask for the register level manual.
+ * PCI chip: http://www.plxtech.com/products/io/pci9080
+ *
+ * Notes:
+ * This board is memory mapped. There is some IO stuff, but it isn't needed.
+ *
+ * I use a pretty loose naming style within the driver (rtd_blah).
+ * All externally visible names should be rtd520_blah.
+ * I use camelCase for structures (and inside them).
+ * I may also use upper CamelCase for function names (old habit).
+ *
+ * This board is somewhat related to the RTD PCI4400 board.
+ *
+ * I borrowed heavily from the ni_mio_common, ni_atmio16d, mite, and
+ * das1800, since they have the best documented code. Driver cb_pcidas64.c
+ * uses the same DMA controller.
+ *
+ * As far as I can tell, the About interrupt doesn't work if Sample is
+ * also enabled. It turns out that About really isn't needed, since
+ * we always count down samples read.
+ *
+ * There was some timer/counter code, but it didn't follow the right API.
+ */
+
+/*
+ * driver status:
+ *
+ * Analog-In supports instruction and command mode.
+ *
+ * With DMA, you can sample at 1.15Mhz with 70% idle on a 400Mhz K6-2
+ * (single channel, 64K read buffer). I get random system lockups when
+ * using DMA with ALI-15xx based systems. I haven't been able to test
+ * any other chipsets. The lockups happen soon after the start of an
+ * acquistion, not in the middle of a long run.
+ *
+ * Without DMA, you can do 620Khz sampling with 20% idle on a 400Mhz K6-2
+ * (with a 256K read buffer).
+ *
+ * Digital-IO and Analog-Out only support instruction mode.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "plx9080.h"
+
+/*
+ * Local Address Space 0 Offsets
+ */
+#define LAS0_USER_IO 0x0008 /* User I/O */
+#define LAS0_ADC 0x0010 /* FIFO Status/Software A/D Start */
+#define FS_DAC1_NOT_EMPTY (1 << 0) /* DAC1 FIFO not empty */
+#define FS_DAC1_HEMPTY (1 << 1) /* DAC1 FIFO half empty */
+#define FS_DAC1_NOT_FULL (1 << 2) /* DAC1 FIFO not full */
+#define FS_DAC2_NOT_EMPTY (1 << 4) /* DAC2 FIFO not empty */
+#define FS_DAC2_HEMPTY (1 << 5) /* DAC2 FIFO half empty */
+#define FS_DAC2_NOT_FULL (1 << 6) /* DAC2 FIFO not full */
+#define FS_ADC_NOT_EMPTY (1 << 8) /* ADC FIFO not empty */
+#define FS_ADC_HEMPTY (1 << 9) /* ADC FIFO half empty */
+#define FS_ADC_NOT_FULL (1 << 10) /* ADC FIFO not full */
+#define FS_DIN_NOT_EMPTY (1 << 12) /* DIN FIFO not empty */
+#define FS_DIN_HEMPTY (1 << 13) /* DIN FIFO half empty */
+#define FS_DIN_NOT_FULL (1 << 14) /* DIN FIFO not full */
+#define LAS0_DAC1 0x0014 /* Software D/A1 Update (w) */
+#define LAS0_DAC2 0x0018 /* Software D/A2 Update (w) */
+#define LAS0_DAC 0x0024 /* Software Simultaneous Update (w) */
+#define LAS0_PACER 0x0028 /* Software Pacer Start/Stop */
+#define LAS0_TIMER 0x002c /* Timer Status/HDIN Software Trig. */
+#define LAS0_IT 0x0030 /* Interrupt Status/Enable */
+#define IRQM_ADC_FIFO_WRITE (1 << 0) /* ADC FIFO Write */
+#define IRQM_CGT_RESET (1 << 1) /* Reset CGT */
+#define IRQM_CGT_PAUSE (1 << 3) /* Pause CGT */
+#define IRQM_ADC_ABOUT_CNT (1 << 4) /* About Counter out */
+#define IRQM_ADC_DELAY_CNT (1 << 5) /* Delay Counter out */
+#define IRQM_ADC_SAMPLE_CNT (1 << 6) /* ADC Sample Counter */
+#define IRQM_DAC1_UCNT (1 << 7) /* DAC1 Update Counter */
+#define IRQM_DAC2_UCNT (1 << 8) /* DAC2 Update Counter */
+#define IRQM_UTC1 (1 << 9) /* User TC1 out */
+#define IRQM_UTC1_INV (1 << 10) /* User TC1 out, inverted */
+#define IRQM_UTC2 (1 << 11) /* User TC2 out */
+#define IRQM_DIGITAL_IT (1 << 12) /* Digital Interrupt */
+#define IRQM_EXTERNAL_IT (1 << 13) /* External Interrupt */
+#define IRQM_ETRIG_RISING (1 << 14) /* Ext Trigger rising-edge */
+#define IRQM_ETRIG_FALLING (1 << 15) /* Ext Trigger falling-edge */
+#define LAS0_CLEAR 0x0034 /* Clear/Set Interrupt Clear Mask */
+#define LAS0_OVERRUN 0x0038 /* Pending interrupts/Clear Overrun */
+#define LAS0_PCLK 0x0040 /* Pacer Clock (24bit) */
+#define LAS0_BCLK 0x0044 /* Burst Clock (10bit) */
+#define LAS0_ADC_SCNT 0x0048 /* A/D Sample counter (10bit) */
+#define LAS0_DAC1_UCNT 0x004c /* D/A1 Update counter (10 bit) */
+#define LAS0_DAC2_UCNT 0x0050 /* D/A2 Update counter (10 bit) */
+#define LAS0_DCNT 0x0054 /* Delay counter (16 bit) */
+#define LAS0_ACNT 0x0058 /* About counter (16 bit) */
+#define LAS0_DAC_CLK 0x005c /* DAC clock (16bit) */
+#define LAS0_UTC0 0x0060 /* 8254 TC Counter 0 */
+#define LAS0_UTC1 0x0064 /* 8254 TC Counter 1 */
+#define LAS0_UTC2 0x0068 /* 8254 TC Counter 2 */
+#define LAS0_UTC_CTRL 0x006c /* 8254 TC Control */
+#define LAS0_DIO0 0x0070 /* Digital I/O Port 0 */
+#define LAS0_DIO1 0x0074 /* Digital I/O Port 1 */
+#define LAS0_DIO0_CTRL 0x0078 /* Digital I/O Control */
+#define LAS0_DIO_STATUS 0x007c /* Digital I/O Status */
+#define LAS0_BOARD_RESET 0x0100 /* Board reset */
+#define LAS0_DMA0_SRC 0x0104 /* DMA 0 Sources select */
+#define LAS0_DMA1_SRC 0x0108 /* DMA 1 Sources select */
+#define LAS0_ADC_CONVERSION 0x010c /* A/D Conversion Signal select */
+#define LAS0_BURST_START 0x0110 /* Burst Clock Start Trigger select */
+#define LAS0_PACER_START 0x0114 /* Pacer Clock Start Trigger select */
+#define LAS0_PACER_STOP 0x0118 /* Pacer Clock Stop Trigger select */
+#define LAS0_ACNT_STOP_ENABLE 0x011c /* About Counter Stop Enable */
+#define LAS0_PACER_REPEAT 0x0120 /* Pacer Start Trigger Mode select */
+#define LAS0_DIN_START 0x0124 /* HiSpd DI Sampling Signal select */
+#define LAS0_DIN_FIFO_CLEAR 0x0128 /* Digital Input FIFO Clear */
+#define LAS0_ADC_FIFO_CLEAR 0x012c /* A/D FIFO Clear */
+#define LAS0_CGT_WRITE 0x0130 /* Channel Gain Table Write */
+#define LAS0_CGL_WRITE 0x0134 /* Channel Gain Latch Write */
+#define LAS0_CG_DATA 0x0138 /* Digital Table Write */
+#define LAS0_CGT_ENABLE 0x013c /* Channel Gain Table Enable */
+#define LAS0_CG_ENABLE 0x0140 /* Digital Table Enable */
+#define LAS0_CGT_PAUSE 0x0144 /* Table Pause Enable */
+#define LAS0_CGT_RESET 0x0148 /* Reset Channel Gain Table */
+#define LAS0_CGT_CLEAR 0x014c /* Clear Channel Gain Table */
+#define LAS0_DAC1_CTRL 0x0150 /* D/A1 output type/range */
+#define LAS0_DAC1_SRC 0x0154 /* D/A1 update source */
+#define LAS0_DAC1_CYCLE 0x0158 /* D/A1 cycle mode */
+#define LAS0_DAC1_RESET 0x015c /* D/A1 FIFO reset */
+#define LAS0_DAC1_FIFO_CLEAR 0x0160 /* D/A1 FIFO clear */
+#define LAS0_DAC2_CTRL 0x0164 /* D/A2 output type/range */
+#define LAS0_DAC2_SRC 0x0168 /* D/A2 update source */
+#define LAS0_DAC2_CYCLE 0x016c /* D/A2 cycle mode */
+#define LAS0_DAC2_RESET 0x0170 /* D/A2 FIFO reset */
+#define LAS0_DAC2_FIFO_CLEAR 0x0174 /* D/A2 FIFO clear */
+#define LAS0_ADC_SCNT_SRC 0x0178 /* A/D Sample Counter Source select */
+#define LAS0_PACER_SELECT 0x0180 /* Pacer Clock select */
+#define LAS0_SBUS0_SRC 0x0184 /* SyncBus 0 Source select */
+#define LAS0_SBUS0_ENABLE 0x0188 /* SyncBus 0 enable */
+#define LAS0_SBUS1_SRC 0x018c /* SyncBus 1 Source select */
+#define LAS0_SBUS1_ENABLE 0x0190 /* SyncBus 1 enable */
+#define LAS0_SBUS2_SRC 0x0198 /* SyncBus 2 Source select */
+#define LAS0_SBUS2_ENABLE 0x019c /* SyncBus 2 enable */
+#define LAS0_ETRG_POLARITY 0x01a4 /* Ext. Trigger polarity select */
+#define LAS0_EINT_POLARITY 0x01a8 /* Ext. Interrupt polarity select */
+#define LAS0_UTC0_CLOCK 0x01ac /* UTC0 Clock select */
+#define LAS0_UTC0_GATE 0x01b0 /* UTC0 Gate select */
+#define LAS0_UTC1_CLOCK 0x01b4 /* UTC1 Clock select */
+#define LAS0_UTC1_GATE 0x01b8 /* UTC1 Gate select */
+#define LAS0_UTC2_CLOCK 0x01bc /* UTC2 Clock select */
+#define LAS0_UTC2_GATE 0x01c0 /* UTC2 Gate select */
+#define LAS0_UOUT0_SELECT 0x01c4 /* User Output 0 source select */
+#define LAS0_UOUT1_SELECT 0x01c8 /* User Output 1 source select */
+#define LAS0_DMA0_RESET 0x01cc /* DMA0 Request state machine reset */
+#define LAS0_DMA1_RESET 0x01d0 /* DMA1 Request state machine reset */
+
+/*
+ * Local Address Space 1 Offsets
+ */
+#define LAS1_ADC_FIFO 0x0000 /* A/D FIFO (16bit) */
+#define LAS1_HDIO_FIFO 0x0004 /* HiSpd DI FIFO (16bit) */
+#define LAS1_DAC1_FIFO 0x0008 /* D/A1 FIFO (16bit) */
+#define LAS1_DAC2_FIFO 0x000c /* D/A2 FIFO (16bit) */
+
+/*======================================================================
+ Driver specific stuff (tunable)
+======================================================================*/
+
+/* We really only need 2 buffers. More than that means being much
+ smarter about knowing which ones are full. */
+#define DMA_CHAIN_COUNT 2 /* max DMA segments/buffers in a ring (min 2) */
+
+/* Target period for periodic transfers. This sets the user read latency. */
+/* Note: There are certain rates where we give this up and transfer 1/2 FIFO */
+/* If this is too low, efficiency is poor */
+#define TRANS_TARGET_PERIOD 10000000 /* 10 ms (in nanoseconds) */
+
+/* Set a practical limit on how long a list to support (affects memory use) */
+/* The board support a channel list up to the FIFO length (1K or 8K) */
+#define RTD_MAX_CHANLIST 128 /* max channel list that we allow */
+
+/*======================================================================
+ Board specific stuff
+======================================================================*/
+
+#define RTD_CLOCK_RATE 8000000 /* 8Mhz onboard clock */
+#define RTD_CLOCK_BASE 125 /* clock period in ns */
+
+/* Note: these speed are slower than the spec, but fit the counter resolution*/
+#define RTD_MAX_SPEED 1625 /* when sampling, in nanoseconds */
+/* max speed if we don't have to wait for settling */
+#define RTD_MAX_SPEED_1 875 /* if single channel, in nanoseconds */
+
+#define RTD_MIN_SPEED 2097151875 /* (24bit counter) in nanoseconds */
+/* min speed when only 1 channel (no burst counter) */
+#define RTD_MIN_SPEED_1 5000000 /* 200Hz, in nanoseconds */
+
+/* Setup continuous ring of 1/2 FIFO transfers. See RTD manual p91 */
+#define DMA_MODE_BITS (\
+ PLX_LOCAL_BUS_16_WIDE_BITS \
+ | PLX_DMA_EN_READYIN_BIT \
+ | PLX_DMA_LOCAL_BURST_EN_BIT \
+ | PLX_EN_CHAIN_BIT \
+ | PLX_DMA_INTR_PCI_BIT \
+ | PLX_LOCAL_ADDR_CONST_BIT \
+ | PLX_DEMAND_MODE_BIT)
+
+#define DMA_TRANSFER_BITS (\
+/* descriptors in PCI memory*/ PLX_DESC_IN_PCI_BIT \
+/* interrupt at end of block */ | PLX_INTR_TERM_COUNT \
+/* from board to PCI */ | PLX_XFER_LOCAL_TO_PCI)
+
+/*======================================================================
+ Comedi specific stuff
+======================================================================*/
+
+/*
+ * The board has 3 input modes and the gains of 1,2,4,...32 (, 64, 128)
+ */
+static const struct comedi_lrange rtd_ai_7520_range = {
+ 18, {
+ /* +-5V input range gain steps */
+ BIP_RANGE(5.0),
+ BIP_RANGE(5.0 / 2),
+ BIP_RANGE(5.0 / 4),
+ BIP_RANGE(5.0 / 8),
+ BIP_RANGE(5.0 / 16),
+ BIP_RANGE(5.0 / 32),
+ /* +-10V input range gain steps */
+ BIP_RANGE(10.0),
+ BIP_RANGE(10.0 / 2),
+ BIP_RANGE(10.0 / 4),
+ BIP_RANGE(10.0 / 8),
+ BIP_RANGE(10.0 / 16),
+ BIP_RANGE(10.0 / 32),
+ /* +10V input range gain steps */
+ UNI_RANGE(10.0),
+ UNI_RANGE(10.0 / 2),
+ UNI_RANGE(10.0 / 4),
+ UNI_RANGE(10.0 / 8),
+ UNI_RANGE(10.0 / 16),
+ UNI_RANGE(10.0 / 32),
+ }
+};
+
+/* PCI4520 has two more gains (6 more entries) */
+static const struct comedi_lrange rtd_ai_4520_range = {
+ 24, {
+ /* +-5V input range gain steps */
+ BIP_RANGE(5.0),
+ BIP_RANGE(5.0 / 2),
+ BIP_RANGE(5.0 / 4),
+ BIP_RANGE(5.0 / 8),
+ BIP_RANGE(5.0 / 16),
+ BIP_RANGE(5.0 / 32),
+ BIP_RANGE(5.0 / 64),
+ BIP_RANGE(5.0 / 128),
+ /* +-10V input range gain steps */
+ BIP_RANGE(10.0),
+ BIP_RANGE(10.0 / 2),
+ BIP_RANGE(10.0 / 4),
+ BIP_RANGE(10.0 / 8),
+ BIP_RANGE(10.0 / 16),
+ BIP_RANGE(10.0 / 32),
+ BIP_RANGE(10.0 / 64),
+ BIP_RANGE(10.0 / 128),
+ /* +10V input range gain steps */
+ UNI_RANGE(10.0),
+ UNI_RANGE(10.0 / 2),
+ UNI_RANGE(10.0 / 4),
+ UNI_RANGE(10.0 / 8),
+ UNI_RANGE(10.0 / 16),
+ UNI_RANGE(10.0 / 32),
+ UNI_RANGE(10.0 / 64),
+ UNI_RANGE(10.0 / 128),
+ }
+};
+
+/* Table order matches range values */
+static const struct comedi_lrange rtd_ao_range = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ }
+};
+
+enum rtd_boardid {
+ BOARD_DM7520,
+ BOARD_PCI4520,
+};
+
+struct rtd_boardinfo {
+ const char *name;
+ int range_bip10; /* start of +-10V range */
+ int range_uni10; /* start of +10V range */
+ const struct comedi_lrange *ai_range;
+};
+
+static const struct rtd_boardinfo rtd520Boards[] = {
+ [BOARD_DM7520] = {
+ .name = "DM7520",
+ .range_bip10 = 6,
+ .range_uni10 = 12,
+ .ai_range = &rtd_ai_7520_range,
+ },
+ [BOARD_PCI4520] = {
+ .name = "PCI4520",
+ .range_bip10 = 8,
+ .range_uni10 = 16,
+ .ai_range = &rtd_ai_4520_range,
+ },
+};
+
+struct rtd_private {
+ /* memory mapped board structures */
+ void __iomem *las1;
+ void __iomem *lcfg;
+
+ long ai_count; /* total transfer size (samples) */
+ int xfer_count; /* # to transfer data. 0->1/2FIFO */
+ int flags; /* flag event modes */
+ unsigned fifosz;
+};
+
+/* bit defines for "flags" */
+#define SEND_EOS 0x01 /* send End Of Scan events */
+#define DMA0_ACTIVE 0x02 /* DMA0 is active */
+#define DMA1_ACTIVE 0x04 /* DMA1 is active */
+
+/*
+ Given a desired period and the clock period (both in ns),
+ return the proper counter value (divider-1).
+ Sets the original period to be the true value.
+ Note: you have to check if the value is larger than the counter range!
+*/
+static int rtd_ns_to_timer_base(unsigned int *nanosec,
+ unsigned int flags, int base)
+{
+ int divider;
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ divider = (*nanosec + base / 2) / base;
+ break;
+ case CMDF_ROUND_DOWN:
+ divider = (*nanosec) / base;
+ break;
+ case CMDF_ROUND_UP:
+ divider = (*nanosec + base - 1) / base;
+ break;
+ }
+ if (divider < 2)
+ divider = 2; /* min is divide by 2 */
+
+ /* Note: we don't check for max, because different timers
+ have different ranges */
+
+ *nanosec = base * divider;
+ return divider - 1; /* countdown is divisor+1 */
+}
+
+/*
+ Given a desired period (in ns),
+ return the proper counter value (divider-1) for the internal clock.
+ Sets the original period to be the true value.
+*/
+static int rtd_ns_to_timer(unsigned int *ns, unsigned int flags)
+{
+ return rtd_ns_to_timer_base(ns, flags, RTD_CLOCK_BASE);
+}
+
+/*
+ Convert a single comedi channel-gain entry to a RTD520 table entry
+*/
+static unsigned short rtd_convert_chan_gain(struct comedi_device *dev,
+ unsigned int chanspec, int index)
+{
+ const struct rtd_boardinfo *board = dev->board_ptr;
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned short r = 0;
+
+ r |= chan & 0xf;
+
+ /* Note: we also setup the channel list bipolar flag array */
+ if (range < board->range_bip10) {
+ /* +-5 range */
+ r |= 0x000;
+ r |= (range & 0x7) << 4;
+ } else if (range < board->range_uni10) {
+ /* +-10 range */
+ r |= 0x100;
+ r |= ((range - board->range_bip10) & 0x7) << 4;
+ } else {
+ /* +10 range */
+ r |= 0x200;
+ r |= ((range - board->range_uni10) & 0x7) << 4;
+ }
+
+ switch (aref) {
+ case AREF_GROUND: /* on-board ground */
+ break;
+
+ case AREF_COMMON:
+ r |= 0x80; /* ref external analog common */
+ break;
+
+ case AREF_DIFF:
+ r |= 0x400; /* differential inputs */
+ break;
+
+ case AREF_OTHER: /* ??? */
+ break;
+ }
+ return r;
+}
+
+/*
+ Setup the channel-gain table from a comedi list
+*/
+static void rtd_load_channelgain_list(struct comedi_device *dev,
+ unsigned int n_chan, unsigned int *list)
+{
+ if (n_chan > 1) { /* setup channel gain table */
+ int ii;
+
+ writel(0, dev->mmio + LAS0_CGT_CLEAR);
+ writel(1, dev->mmio + LAS0_CGT_ENABLE);
+ for (ii = 0; ii < n_chan; ii++) {
+ writel(rtd_convert_chan_gain(dev, list[ii], ii),
+ dev->mmio + LAS0_CGT_WRITE);
+ }
+ } else { /* just use the channel gain latch */
+ writel(0, dev->mmio + LAS0_CGT_ENABLE);
+ writel(rtd_convert_chan_gain(dev, list[0], 0),
+ dev->mmio + LAS0_CGL_WRITE);
+ }
+}
+
+/* determine fifo size by doing adc conversions until the fifo half
+empty status flag clears */
+static int rtd520_probe_fifo_depth(struct comedi_device *dev)
+{
+ unsigned int chanspec = CR_PACK(0, 0, AREF_GROUND);
+ unsigned i;
+ static const unsigned limit = 0x2000;
+ unsigned fifo_size = 0;
+
+ writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR);
+ rtd_load_channelgain_list(dev, 1, &chanspec);
+ /* ADC conversion trigger source: SOFTWARE */
+ writel(0, dev->mmio + LAS0_ADC_CONVERSION);
+ /* convert samples */
+ for (i = 0; i < limit; ++i) {
+ unsigned fifo_status;
+ /* trigger conversion */
+ writew(0, dev->mmio + LAS0_ADC);
+ udelay(1);
+ fifo_status = readl(dev->mmio + LAS0_ADC);
+ if ((fifo_status & FS_ADC_HEMPTY) == 0) {
+ fifo_size = 2 * i;
+ break;
+ }
+ }
+ if (i == limit) {
+ dev_info(dev->class_dev, "failed to probe fifo size.\n");
+ return -EIO;
+ }
+ writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR);
+ if (fifo_size != 0x400 && fifo_size != 0x2000) {
+ dev_info(dev->class_dev,
+ "unexpected fifo size of %i, expected 1024 or 8192.\n",
+ fifo_size);
+ return -EIO;
+ }
+ return fifo_size;
+}
+
+static int rtd_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readl(dev->mmio + LAS0_ADC);
+ if (status & FS_ADC_NOT_EMPTY)
+ return 0;
+ return -EBUSY;
+}
+
+static int rtd_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rtd_private *devpriv = dev->private;
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int ret;
+ int n;
+
+ /* clear any old fifo data */
+ writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR);
+
+ /* write channel to multiplexer and clear channel gain table */
+ rtd_load_channelgain_list(dev, 1, &insn->chanspec);
+
+ /* ADC conversion trigger source: SOFTWARE */
+ writel(0, dev->mmio + LAS0_ADC_CONVERSION);
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ unsigned short d;
+ /* trigger conversion */
+ writew(0, dev->mmio + LAS0_ADC);
+
+ ret = comedi_timeout(dev, s, insn, rtd_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ d = readw(devpriv->las1 + LAS1_ADC_FIFO);
+ d >>= 3; /* low 3 bits are marker lines */
+
+ /* convert bipolar data to comedi unsigned data */
+ if (comedi_range_is_bipolar(s, range))
+ d = comedi_offset_munge(s, d);
+
+ data[n] = d & s->maxdata;
+ }
+
+ /* return the number of samples read/written */
+ return n;
+}
+
+/*
+ Get what we know is there.... Fast!
+ This uses 1/2 the bus cycles of read_dregs (below).
+
+ The manual claims that we can do a lword read, but it doesn't work here.
+*/
+static int ai_read_n(struct comedi_device *dev, struct comedi_subdevice *s,
+ int count)
+{
+ struct rtd_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int ii;
+
+ for (ii = 0; ii < count; ii++) {
+ unsigned int range = CR_RANGE(cmd->chanlist[async->cur_chan]);
+ unsigned short d;
+
+ if (0 == devpriv->ai_count) { /* done */
+ d = readw(devpriv->las1 + LAS1_ADC_FIFO);
+ continue;
+ }
+
+ d = readw(devpriv->las1 + LAS1_ADC_FIFO);
+ d >>= 3; /* low 3 bits are marker lines */
+
+ /* convert bipolar data to comedi unsigned data */
+ if (comedi_range_is_bipolar(s, range))
+ d = comedi_offset_munge(s, d);
+ d &= s->maxdata;
+
+ if (!comedi_buf_write_samples(s, &d, 1))
+ return -1;
+
+ if (devpriv->ai_count > 0) /* < 0, means read forever */
+ devpriv->ai_count--;
+ }
+ return 0;
+}
+
+/*
+ Handle all rtd520 interrupts.
+ Runs atomically and is never re-entered.
+ This is a "slow handler"; other interrupts may be active.
+ The data conversion may someday happen in a "bottom half".
+*/
+static irqreturn_t rtd_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct rtd_private *devpriv = dev->private;
+ u32 overrun;
+ u16 status;
+ u16 fifo_status;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ fifo_status = readl(dev->mmio + LAS0_ADC);
+ /* check for FIFO full, this automatically halts the ADC! */
+ if (!(fifo_status & FS_ADC_NOT_FULL)) /* 0 -> full */
+ goto xfer_abort;
+
+ status = readw(dev->mmio + LAS0_IT);
+ /* if interrupt was not caused by our board, or handled above */
+ if (0 == status)
+ return IRQ_HANDLED;
+
+ if (status & IRQM_ADC_ABOUT_CNT) { /* sample count -> read FIFO */
+ /*
+ * since the priority interrupt controller may have queued
+ * a sample counter interrupt, even though we have already
+ * finished, we must handle the possibility that there is
+ * no data here
+ */
+ if (!(fifo_status & FS_ADC_HEMPTY)) {
+ /* FIFO half full */
+ if (ai_read_n(dev, s, devpriv->fifosz / 2) < 0)
+ goto xfer_abort;
+
+ if (0 == devpriv->ai_count)
+ goto xfer_done;
+ } else if (devpriv->xfer_count > 0) {
+ if (fifo_status & FS_ADC_NOT_EMPTY) {
+ /* FIFO not empty */
+ if (ai_read_n(dev, s, devpriv->xfer_count) < 0)
+ goto xfer_abort;
+
+ if (0 == devpriv->ai_count)
+ goto xfer_done;
+ }
+ }
+ }
+
+ overrun = readl(dev->mmio + LAS0_OVERRUN) & 0xffff;
+ if (overrun)
+ goto xfer_abort;
+
+ /* clear the interrupt */
+ writew(status, dev->mmio + LAS0_CLEAR);
+ readw(dev->mmio + LAS0_CLEAR);
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+
+xfer_abort:
+ s->async->events |= COMEDI_CB_ERROR;
+
+xfer_done:
+ s->async->events |= COMEDI_CB_EOA;
+
+ /* clear the interrupt */
+ status = readw(dev->mmio + LAS0_IT);
+ writew(status, dev->mmio + LAS0_CLEAR);
+ readw(dev->mmio + LAS0_CLEAR);
+
+ fifo_status = readl(dev->mmio + LAS0_ADC);
+ overrun = readl(dev->mmio + LAS0_OVERRUN) & 0xffff;
+
+ comedi_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ cmdtest tests a particular command to see if it is valid.
+ Using the cmdtest ioctl, a user can create a valid cmd
+ and then have it executed by the cmd ioctl (asynchronously).
+
+ cmdtest returns 1,2,3,4 or 0, depending on which tests
+ the command passes.
+*/
+
+static int rtd_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* Note: these are time periods, not actual rates */
+ if (1 == cmd->chanlist_len) { /* no scanning */
+ if (comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ RTD_MAX_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ CMDF_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ RTD_MIN_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ CMDF_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ } else {
+ if (comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ RTD_MAX_SPEED)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ CMDF_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ RTD_MIN_SPEED)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ CMDF_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ }
+ } else {
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ /* should specify multiple external triggers */
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 9);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ if (1 == cmd->chanlist_len) { /* no scanning */
+ if (comedi_check_trigger_arg_min(&cmd->convert_arg,
+ RTD_MAX_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ CMDF_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (comedi_check_trigger_arg_max(&cmd->convert_arg,
+ RTD_MIN_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ CMDF_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ } else {
+ if (comedi_check_trigger_arg_min(&cmd->convert_arg,
+ RTD_MAX_SPEED)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ CMDF_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (comedi_check_trigger_arg_max(&cmd->convert_arg,
+ RTD_MIN_SPEED)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ CMDF_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ }
+ } else {
+ /* external trigger */
+ /* see above */
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg, 9);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ rtd_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ rtd_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+/*
+ Execute a analog in command with many possible triggering options.
+ The data get stored in the async structure of the subdevice.
+ This is usually done by an interrupt handler.
+ Userland gets to the data using read calls.
+*/
+static int rtd_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct rtd_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int timer;
+
+ /* stop anything currently running */
+ /* pacer stop source: SOFTWARE */
+ writel(0, dev->mmio + LAS0_PACER_STOP);
+ writel(0, dev->mmio + LAS0_PACER); /* stop pacer */
+ writel(0, dev->mmio + LAS0_ADC_CONVERSION);
+ writew(0, dev->mmio + LAS0_IT);
+ writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR);
+ writel(0, dev->mmio + LAS0_OVERRUN);
+
+ /* start configuration */
+ /* load channel list and reset CGT */
+ rtd_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist);
+
+ /* setup the common case and override if needed */
+ if (cmd->chanlist_len > 1) {
+ /* pacer start source: SOFTWARE */
+ writel(0, dev->mmio + LAS0_PACER_START);
+ /* burst trigger source: PACER */
+ writel(1, dev->mmio + LAS0_BURST_START);
+ /* ADC conversion trigger source: BURST */
+ writel(2, dev->mmio + LAS0_ADC_CONVERSION);
+ } else { /* single channel */
+ /* pacer start source: SOFTWARE */
+ writel(0, dev->mmio + LAS0_PACER_START);
+ /* ADC conversion trigger source: PACER */
+ writel(1, dev->mmio + LAS0_ADC_CONVERSION);
+ }
+ writel((devpriv->fifosz / 2 - 1) & 0xffff, dev->mmio + LAS0_ACNT);
+
+ if (TRIG_TIMER == cmd->scan_begin_src) {
+ /* scan_begin_arg is in nanoseconds */
+ /* find out how many samples to wait before transferring */
+ if (cmd->flags & CMDF_WAKE_EOS) {
+ /*
+ * this may generate un-sustainable interrupt rates
+ * the application is responsible for doing the
+ * right thing
+ */
+ devpriv->xfer_count = cmd->chanlist_len;
+ devpriv->flags |= SEND_EOS;
+ } else {
+ /* arrange to transfer data periodically */
+ devpriv->xfer_count =
+ (TRANS_TARGET_PERIOD * cmd->chanlist_len) /
+ cmd->scan_begin_arg;
+ if (devpriv->xfer_count < cmd->chanlist_len) {
+ /* transfer after each scan (and avoid 0) */
+ devpriv->xfer_count = cmd->chanlist_len;
+ } else { /* make a multiple of scan length */
+ devpriv->xfer_count =
+ (devpriv->xfer_count +
+ cmd->chanlist_len - 1)
+ / cmd->chanlist_len;
+ devpriv->xfer_count *= cmd->chanlist_len;
+ }
+ devpriv->flags |= SEND_EOS;
+ }
+ if (devpriv->xfer_count >= (devpriv->fifosz / 2)) {
+ /* out of counter range, use 1/2 fifo instead */
+ devpriv->xfer_count = 0;
+ devpriv->flags &= ~SEND_EOS;
+ } else {
+ /* interrupt for each transfer */
+ writel((devpriv->xfer_count - 1) & 0xffff,
+ dev->mmio + LAS0_ACNT);
+ }
+ } else { /* unknown timing, just use 1/2 FIFO */
+ devpriv->xfer_count = 0;
+ devpriv->flags &= ~SEND_EOS;
+ }
+ /* pacer clock source: INTERNAL 8MHz */
+ writel(1, dev->mmio + LAS0_PACER_SELECT);
+ /* just interrupt, don't stop */
+ writel(1, dev->mmio + LAS0_ACNT_STOP_ENABLE);
+
+ /* BUG??? these look like enumerated values, but they are bit fields */
+
+ /* First, setup when to stop */
+ switch (cmd->stop_src) {
+ case TRIG_COUNT: /* stop after N scans */
+ devpriv->ai_count = cmd->stop_arg * cmd->chanlist_len;
+ if ((devpriv->xfer_count > 0)
+ && (devpriv->xfer_count > devpriv->ai_count)) {
+ devpriv->xfer_count = devpriv->ai_count;
+ }
+ break;
+
+ case TRIG_NONE: /* stop when cancel is called */
+ devpriv->ai_count = -1; /* read forever */
+ break;
+ }
+
+ /* Scan timing */
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER: /* periodic scanning */
+ timer = rtd_ns_to_timer(&cmd->scan_begin_arg,
+ CMDF_ROUND_NEAREST);
+ /* set PACER clock */
+ writel(timer & 0xffffff, dev->mmio + LAS0_PCLK);
+
+ break;
+
+ case TRIG_EXT:
+ /* pacer start source: EXTERNAL */
+ writel(1, dev->mmio + LAS0_PACER_START);
+ break;
+ }
+
+ /* Sample timing within a scan */
+ switch (cmd->convert_src) {
+ case TRIG_TIMER: /* periodic */
+ if (cmd->chanlist_len > 1) {
+ /* only needed for multi-channel */
+ timer = rtd_ns_to_timer(&cmd->convert_arg,
+ CMDF_ROUND_NEAREST);
+ /* setup BURST clock */
+ writel(timer & 0x3ff, dev->mmio + LAS0_BCLK);
+ }
+
+ break;
+
+ case TRIG_EXT: /* external */
+ /* burst trigger source: EXTERNAL */
+ writel(2, dev->mmio + LAS0_BURST_START);
+ break;
+ }
+ /* end configuration */
+
+ /* This doesn't seem to work. There is no way to clear an interrupt
+ that the priority controller has queued! */
+ writew(~0, dev->mmio + LAS0_CLEAR);
+ readw(dev->mmio + LAS0_CLEAR);
+
+ /* TODO: allow multiple interrupt sources */
+ /* transfer every N samples */
+ writew(IRQM_ADC_ABOUT_CNT, dev->mmio + LAS0_IT);
+
+ /* BUG: start_src is ASSUMED to be TRIG_NOW */
+ /* BUG? it seems like things are running before the "start" */
+ readl(dev->mmio + LAS0_PACER); /* start pacer */
+ return 0;
+}
+
+/*
+ Stop a running data acquisition.
+*/
+static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ /* pacer stop source: SOFTWARE */
+ writel(0, dev->mmio + LAS0_PACER_STOP);
+ writel(0, dev->mmio + LAS0_PACER); /* stop pacer */
+ writel(0, dev->mmio + LAS0_ADC_CONVERSION);
+ writew(0, dev->mmio + LAS0_IT);
+ devpriv->ai_count = 0; /* stop and don't transfer any more */
+ writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR);
+ return 0;
+}
+
+static int rtd_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int bit = (chan == 0) ? FS_DAC1_NOT_EMPTY : FS_DAC2_NOT_EMPTY;
+ unsigned int status;
+
+ status = readl(dev->mmio + LAS0_ADC);
+ if (status & bit)
+ return 0;
+ return -EBUSY;
+}
+
+static int rtd_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rtd_private *devpriv = dev->private;
+ int i;
+ int chan = CR_CHAN(insn->chanspec);
+ int range = CR_RANGE(insn->chanspec);
+ int ret;
+
+ /* Configure the output range (table index matches the range values) */
+ writew(range & 7,
+ dev->mmio + ((chan == 0) ? LAS0_DAC1_CTRL : LAS0_DAC2_CTRL));
+
+ /* Writing a list of values to an AO channel is probably not
+ * very useful, but that's how the interface is defined. */
+ for (i = 0; i < insn->n; ++i) {
+ int val = data[i] << 3;
+
+ /* VERIFY: comedi range and offset conversions */
+
+ if ((range > 1) /* bipolar */
+ && (data[i] < 2048)) {
+ /* offset and sign extend */
+ val = (((int)data[i]) - 2048) << 3;
+ } else { /* unipolor */
+ val = data[i] << 3;
+ }
+
+ /* a typical programming sequence */
+ writew(val, devpriv->las1 +
+ ((chan == 0) ? LAS1_DAC1_FIFO : LAS1_DAC2_FIFO));
+ writew(0, dev->mmio + ((chan == 0) ? LAS0_DAC1 : LAS0_DAC2));
+
+ s->readback[chan] = data[i];
+
+ ret = comedi_timeout(dev, s, insn, rtd_ao_eoc, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* return the number of samples read/written */
+ return i;
+}
+
+static int rtd_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ writew(s->state & 0xff, dev->mmio + LAS0_DIO0);
+
+ data[1] = readw(dev->mmio + LAS0_DIO0) & 0xff;
+
+ return insn->n;
+}
+
+static int rtd_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /* TODO support digital match interrupts and strobes */
+
+ /* set direction */
+ writew(0x01, dev->mmio + LAS0_DIO_STATUS);
+ writew(s->io_bits & 0xff, dev->mmio + LAS0_DIO0_CTRL);
+
+ /* clear interrupts */
+ writew(0x00, dev->mmio + LAS0_DIO_STATUS);
+
+ /* port1 can only be all input or all output */
+
+ /* there are also 2 user input lines and 2 user output lines */
+
+ return insn->n;
+}
+
+static void rtd_reset(struct comedi_device *dev)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ writel(0, dev->mmio + LAS0_BOARD_RESET);
+ udelay(100); /* needed? */
+ writel(0, devpriv->lcfg + PLX_INTRCS_REG);
+ writew(0, dev->mmio + LAS0_IT);
+ writew(~0, dev->mmio + LAS0_CLEAR);
+ readw(dev->mmio + LAS0_CLEAR);
+}
+
+/*
+ * initialize board, per RTD spec
+ * also, initialize shadow registers
+ */
+static void rtd_init_board(struct comedi_device *dev)
+{
+ rtd_reset(dev);
+
+ writel(0, dev->mmio + LAS0_OVERRUN);
+ writel(0, dev->mmio + LAS0_CGT_CLEAR);
+ writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR);
+ writel(0, dev->mmio + LAS0_DAC1_RESET);
+ writel(0, dev->mmio + LAS0_DAC2_RESET);
+ /* clear digital IO fifo */
+ writew(0, dev->mmio + LAS0_DIO_STATUS);
+ writeb((0 << 6) | 0x30, dev->mmio + LAS0_UTC_CTRL);
+ writeb((1 << 6) | 0x30, dev->mmio + LAS0_UTC_CTRL);
+ writeb((2 << 6) | 0x30, dev->mmio + LAS0_UTC_CTRL);
+ writeb((3 << 6) | 0x00, dev->mmio + LAS0_UTC_CTRL);
+ /* TODO: set user out source ??? */
+}
+
+/* The RTD driver does this */
+static void rtd_pci_latency_quirk(struct comedi_device *dev,
+ struct pci_dev *pcidev)
+{
+ unsigned char pci_latency;
+
+ pci_read_config_byte(pcidev, PCI_LATENCY_TIMER, &pci_latency);
+ if (pci_latency < 32) {
+ dev_info(dev->class_dev,
+ "PCI latency changed from %d to %d\n",
+ pci_latency, 32);
+ pci_write_config_byte(pcidev, PCI_LATENCY_TIMER, 32);
+ }
+}
+
+static int rtd_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct rtd_boardinfo *board = NULL;
+ struct rtd_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(rtd520Boards))
+ board = &rtd520Boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 2);
+ devpriv->las1 = pci_ioremap_bar(pcidev, 3);
+ devpriv->lcfg = pci_ioremap_bar(pcidev, 0);
+ if (!dev->mmio || !devpriv->las1 || !devpriv->lcfg)
+ return -ENOMEM;
+
+ rtd_pci_latency_quirk(dev, pcidev);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, rtd_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->range_table = board->ai_range;
+ s->len_chanlist = RTD_MAX_CHANLIST;
+ s->insn_read = rtd_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmd = rtd_ai_cmd;
+ s->do_cmdtest = rtd_ai_cmdtest;
+ s->cancel = rtd_ai_cancel;
+ }
+
+ s = &dev->subdevices[1];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &rtd_ao_range;
+ s->insn_write = rtd_ao_winsn;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ /* digital i/o subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ /* we only support port 0 right now. Ignoring port 1 and user IO */
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = rtd_dio_insn_bits;
+ s->insn_config = rtd_dio_insn_config;
+
+ /* timer/counter subdevices (not currently supported) */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 3;
+ s->maxdata = 0xffff;
+
+ rtd_init_board(dev);
+
+ ret = rtd520_probe_fifo_depth(dev);
+ if (ret < 0)
+ return ret;
+ devpriv->fifosz = ret;
+
+ if (dev->irq)
+ writel(ICS_PIE | ICS_PLIE, devpriv->lcfg + PLX_INTRCS_REG);
+
+ return 0;
+}
+
+static void rtd_detach(struct comedi_device *dev)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ if (devpriv) {
+ /* Shut down any board ops by resetting it */
+ if (dev->mmio && devpriv->lcfg)
+ rtd_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (dev->mmio)
+ iounmap(dev->mmio);
+ if (devpriv->las1)
+ iounmap(devpriv->las1);
+ if (devpriv->lcfg)
+ iounmap(devpriv->lcfg);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver rtd520_driver = {
+ .driver_name = "rtd520",
+ .module = THIS_MODULE,
+ .auto_attach = rtd_auto_attach,
+ .detach = rtd_detach,
+};
+
+static int rtd520_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &rtd520_driver, id->driver_data);
+}
+
+static const struct pci_device_id rtd520_pci_table[] = {
+ { PCI_VDEVICE(RTD, 0x7520), BOARD_DM7520 },
+ { PCI_VDEVICE(RTD, 0x4520), BOARD_PCI4520 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, rtd520_pci_table);
+
+static struct pci_driver rtd520_pci_driver = {
+ .name = "rtd520",
+ .id_table = rtd520_pci_table,
+ .probe = rtd520_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(rtd520_driver, rtd520_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/rti800.c b/drivers/staging/comedi/drivers/rti800.c
new file mode 100644
index 000000000..340ac776e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/rti800.c
@@ -0,0 +1,362 @@
+/*
+ * comedi/drivers/rti800.c
+ * Hardware driver for Analog Devices RTI-800/815 board
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: rti800
+ * Description: Analog Devices RTI-800/815
+ * Devices: [Analog Devices] RTI-800 (rti800), RTI-815 (rti815)
+ * Author: David A. Schleef <ds@schleef.org>
+ * Status: unknown
+ * Updated: Fri, 05 Sep 2008 14:50:44 +0100
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ * [1] - IRQ (not supported / unused)
+ * [2] - A/D mux/reference (number of channels)
+ * 0 = differential
+ * 1 = pseudodifferential (common)
+ * 2 = single-ended
+ * [3] - A/D range
+ * 0 = [-10,10]
+ * 1 = [-5,5]
+ * 2 = [0,10]
+ * [4] - A/D encoding
+ * 0 = two's complement
+ * 1 = straight binary
+ * [5] - DAC 0 range
+ * 0 = [-10,10]
+ * 1 = [0,10]
+ * [6] - DAC 0 encoding
+ * 0 = two's complement
+ * 1 = straight binary
+ * [7] - DAC 1 range (same as DAC 0)
+ * [8] - DAC 1 encoding (same as DAC 0)
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+/*
+ * Register map
+ */
+#define RTI800_CSR 0x00
+#define RTI800_CSR_BUSY (1 << 7)
+#define RTI800_CSR_DONE (1 << 6)
+#define RTI800_CSR_OVERRUN (1 << 5)
+#define RTI800_CSR_TCR (1 << 4)
+#define RTI800_CSR_DMA_ENAB (1 << 3)
+#define RTI800_CSR_INTR_TC (1 << 2)
+#define RTI800_CSR_INTR_EC (1 << 1)
+#define RTI800_CSR_INTR_OVRN (1 << 0)
+#define RTI800_MUXGAIN 0x01
+#define RTI800_CONVERT 0x02
+#define RTI800_ADCLO 0x03
+#define RTI800_ADCHI 0x04
+#define RTI800_DAC0LO 0x05
+#define RTI800_DAC0HI 0x06
+#define RTI800_DAC1LO 0x07
+#define RTI800_DAC1HI 0x08
+#define RTI800_CLRFLAGS 0x09
+#define RTI800_DI 0x0a
+#define RTI800_DO 0x0b
+#define RTI800_9513A_DATA 0x0c
+#define RTI800_9513A_CNTRL 0x0d
+#define RTI800_9513A_STATUS 0x0d
+
+static const struct comedi_lrange range_rti800_ai_10_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_rti800_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_rti800_ai_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange *const rti800_ai_ranges[] = {
+ &range_rti800_ai_10_bipolar,
+ &range_rti800_ai_5_bipolar,
+ &range_rti800_ai_unipolar,
+};
+
+static const struct comedi_lrange *const rti800_ao_ranges[] = {
+ &range_bipolar10,
+ &range_unipolar10,
+};
+
+struct rti800_board {
+ const char *name;
+ int has_ao;
+};
+
+static const struct rti800_board rti800_boardtypes[] = {
+ {
+ .name = "rti800",
+ }, {
+ .name = "rti815",
+ .has_ao = 1,
+ },
+};
+
+struct rti800_private {
+ bool adc_2comp;
+ bool dac_2comp[2];
+ const struct comedi_lrange *ao_range_type_list[2];
+ unsigned char muxgain_bits;
+};
+
+static int rti800_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + RTI800_CSR);
+ if (status & RTI800_CSR_OVERRUN) {
+ outb(0, dev->iobase + RTI800_CLRFLAGS);
+ return -EOVERFLOW;
+ }
+ if (status & RTI800_CSR_DONE)
+ return 0;
+ return -EBUSY;
+}
+
+static int rti800_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti800_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int gain = CR_RANGE(insn->chanspec);
+ unsigned char muxgain_bits;
+ int ret;
+ int i;
+
+ inb(dev->iobase + RTI800_ADCHI);
+ outb(0, dev->iobase + RTI800_CLRFLAGS);
+
+ muxgain_bits = chan | (gain << 5);
+ if (muxgain_bits != devpriv->muxgain_bits) {
+ devpriv->muxgain_bits = muxgain_bits;
+ outb(devpriv->muxgain_bits, dev->iobase + RTI800_MUXGAIN);
+ /*
+ * Without a delay here, the RTI_CSR_OVERRUN bit
+ * gets set, and you will have an error.
+ */
+ if (insn->n > 0) {
+ int delay = (gain == 0) ? 10 :
+ (gain == 1) ? 20 :
+ (gain == 2) ? 40 : 80;
+
+ udelay(delay);
+ }
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ outb(0, dev->iobase + RTI800_CONVERT);
+
+ ret = comedi_timeout(dev, s, insn, rti800_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = inb(dev->iobase + RTI800_ADCLO);
+ data[i] |= (inb(dev->iobase + RTI800_ADCHI) & 0xf) << 8;
+
+ if (devpriv->adc_2comp)
+ data[i] ^= 0x800;
+ }
+
+ return insn->n;
+}
+
+static int rti800_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti800_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int reg_lo = chan ? RTI800_DAC1LO : RTI800_DAC0LO;
+ int reg_hi = chan ? RTI800_DAC1HI : RTI800_DAC0HI;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ if (devpriv->dac_2comp[chan])
+ val ^= 0x800;
+
+ outb(val & 0xff, dev->iobase + reg_lo);
+ outb((val >> 8) & 0xff, dev->iobase + reg_hi);
+ }
+
+ return insn->n;
+}
+
+static int rti800_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + RTI800_DI);
+ return insn->n;
+}
+
+static int rti800_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ /* Outputs are inverted... */
+ outb(s->state ^ 0xff, dev->iobase + RTI800_DO);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int rti800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct rti800_board *board = dev->board_ptr;
+ struct rti800_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ outb(0, dev->iobase + RTI800_CSR);
+ inb(dev->iobase + RTI800_ADCHI);
+ outb(0, dev->iobase + RTI800_CLRFLAGS);
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->adc_2comp = (it->options[4] == 0);
+ devpriv->dac_2comp[0] = (it->options[6] == 0);
+ devpriv->dac_2comp[1] = (it->options[8] == 0);
+ /* invalid, forces the MUXGAIN register to be set when first used */
+ devpriv->muxgain_bits = 0xff;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = (it->options[2] ? 16 : 8);
+ s->insn_read = rti800_ai_insn_read;
+ s->maxdata = 0x0fff;
+ s->range_table = (it->options[3] < ARRAY_SIZE(rti800_ai_ranges))
+ ? rti800_ai_ranges[it->options[3]]
+ : &range_unknown;
+
+ s = &dev->subdevices[1];
+ if (board->has_ao) {
+ /* ao subdevice (only on rti815) */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table_list = devpriv->ao_range_type_list;
+ devpriv->ao_range_type_list[0] =
+ (it->options[5] < ARRAY_SIZE(rti800_ao_ranges))
+ ? rti800_ao_ranges[it->options[5]]
+ : &range_unknown;
+ devpriv->ao_range_type_list[1] =
+ (it->options[7] < ARRAY_SIZE(rti800_ao_ranges))
+ ? rti800_ao_ranges[it->options[7]]
+ : &range_unknown;
+ s->insn_write = rti800_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* di */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->insn_bits = rti800_di_insn_bits;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+
+ s = &dev->subdevices[3];
+ /* do */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->insn_bits = rti800_do_insn_bits;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+
+ /*
+ * There is also an Am9513 timer on these boards. This subdevice
+ * is not currently supported.
+ */
+
+ return 0;
+}
+
+static struct comedi_driver rti800_driver = {
+ .driver_name = "rti800",
+ .module = THIS_MODULE,
+ .attach = rti800_attach,
+ .detach = comedi_legacy_detach,
+ .num_names = ARRAY_SIZE(rti800_boardtypes),
+ .board_name = &rti800_boardtypes[0].name,
+ .offset = sizeof(struct rti800_board),
+};
+module_comedi_driver(rti800_driver);
+
+MODULE_DESCRIPTION("Comedi: RTI-800 Multifunction Analog/Digital board");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/rti802.c b/drivers/staging/comedi/drivers/rti802.c
new file mode 100644
index 000000000..6db58fcfd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/rti802.c
@@ -0,0 +1,129 @@
+/*
+ * rti802.c
+ * Comedi driver for Analog Devices RTI-802 board
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: rti802
+ * Description: Analog Devices RTI-802
+ * Author: Anders Blomdell <anders.blomdell@control.lth.se>
+ * Devices: [Analog Devices] RTI-802 (rti802)
+ * Status: works
+ *
+ * Configuration Options:
+ * [0] - i/o base
+ * [1] - unused
+ * [2,4,6,8,10,12,14,16] - dac#[0-7] 0=two's comp, 1=straight
+ * [3,5,7,9,11,13,15,17] - dac#[0-7] 0=bipolar, 1=unipolar
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define RTI802_SELECT 0x00
+#define RTI802_DATALOW 0x01
+#define RTI802_DATAHIGH 0x02
+
+struct rti802_private {
+ enum {
+ dac_2comp, dac_straight
+ } dac_coding[8];
+ const struct comedi_lrange *range_type_list[8];
+};
+
+static int rti802_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti802_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ outb(chan, dev->iobase + RTI802_SELECT);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val = data[i];
+
+ s->readback[chan] = val;
+
+ /* munge offset binary to two's complement if needed */
+ if (devpriv->dac_coding[chan] == dac_2comp)
+ val = comedi_offset_munge(s, val);
+
+ outb(val & 0xff, dev->iobase + RTI802_DATALOW);
+ outb((val >> 8) & 0xff, dev->iobase + RTI802_DATAHIGH);
+ }
+
+ return insn->n;
+}
+
+static int rti802_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct rti802_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x04);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->maxdata = 0xfff;
+ s->n_chan = 8;
+ s->insn_write = rti802_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s->range_table_list = devpriv->range_type_list;
+ for (i = 0; i < 8; i++) {
+ devpriv->dac_coding[i] = (it->options[3 + 2 * i])
+ ? (dac_straight) : (dac_2comp);
+ devpriv->range_type_list[i] = (it->options[2 + 2 * i])
+ ? &range_unipolar10 : &range_bipolar10;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver rti802_driver = {
+ .driver_name = "rti802",
+ .module = THIS_MODULE,
+ .attach = rti802_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(rti802_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Analog Devices RTI-802 board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/s526.c b/drivers/staging/comedi/drivers/s526.c
new file mode 100644
index 000000000..6f3e8a08e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/s526.c
@@ -0,0 +1,614 @@
+/*
+ comedi/drivers/s526.c
+ Sensoray s526 Comedi driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: s526
+Description: Sensoray 526 driver
+Devices: [Sensoray] 526 (s526)
+Author: Richie
+ Everett Wang <everett.wang@everteq.com>
+Updated: Thu, 14 Sep. 2006
+Status: experimental
+
+Encoder works
+Analog input works
+Analog output works
+PWM output works
+Commands are not supported yet.
+
+Configuration Options:
+
+comedi_config /dev/comedi0 s526 0x2C0,0x3
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+#include <asm/byteorder.h>
+
+#define S526_START_AI_CONV 0
+#define S526_AI_READ 0
+
+/* Ports */
+#define S526_NUM_PORTS 27
+
+/* registers */
+#define REG_TCR 0x00
+#define REG_WDC 0x02
+#define REG_DAC 0x04
+#define REG_ADC 0x06
+#define REG_ADD 0x08
+#define REG_DIO 0x0A
+#define REG_IER 0x0C
+#define REG_ISR 0x0E
+#define REG_MSC 0x10
+#define REG_C0L 0x12
+#define REG_C0H 0x14
+#define REG_C0M 0x16
+#define REG_C0C 0x18
+#define REG_C1L 0x1A
+#define REG_C1H 0x1C
+#define REG_C1M 0x1E
+#define REG_C1C 0x20
+#define REG_C2L 0x22
+#define REG_C2H 0x24
+#define REG_C2M 0x26
+#define REG_C2C 0x28
+#define REG_C3L 0x2A
+#define REG_C3H 0x2C
+#define REG_C3M 0x2E
+#define REG_C3C 0x30
+#define REG_EED 0x32
+#define REG_EEC 0x34
+
+struct counter_mode_register_t {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ unsigned short coutSource:1;
+ unsigned short coutPolarity:1;
+ unsigned short autoLoadResetRcap:3;
+ unsigned short hwCtEnableSource:2;
+ unsigned short ctEnableCtrl:2;
+ unsigned short clockSource:2;
+ unsigned short countDir:1;
+ unsigned short countDirCtrl:1;
+ unsigned short outputRegLatchCtrl:1;
+ unsigned short preloadRegSel:1;
+ unsigned short reserved:1;
+ #elif defined(__BIG_ENDIAN_BITFIELD)
+ unsigned short reserved:1;
+ unsigned short preloadRegSel:1;
+ unsigned short outputRegLatchCtrl:1;
+ unsigned short countDirCtrl:1;
+ unsigned short countDir:1;
+ unsigned short clockSource:2;
+ unsigned short ctEnableCtrl:2;
+ unsigned short hwCtEnableSource:2;
+ unsigned short autoLoadResetRcap:3;
+ unsigned short coutPolarity:1;
+ unsigned short coutSource:1;
+#else
+#error Unknown bit field order
+#endif
+};
+
+union cmReg {
+ struct counter_mode_register_t reg;
+ unsigned short value;
+};
+
+struct s526_private {
+ unsigned int gpct_config[4];
+ unsigned short ai_config;
+};
+
+static int s526_gpct_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long chan_iobase = dev->iobase + chan * 8;
+ unsigned int lo;
+ unsigned int hi;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Read the low word first */
+ lo = inw(chan_iobase + REG_C0L) & 0xffff;
+ hi = inw(chan_iobase + REG_C0H) & 0xff;
+
+ data[i] = (hi << 16) | lo;
+ }
+
+ return insn->n;
+}
+
+static int s526_gpct_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long chan_iobase = dev->iobase + chan * 8;
+ unsigned int val;
+ union cmReg cmReg;
+
+ /* Check what type of Counter the user requested, data[0] contains */
+ /* the Application type */
+ switch (data[0]) {
+ case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
+ /*
+ data[0]: Application Type
+ data[1]: Counter Mode Register Value
+ data[2]: Pre-load Register Value
+ data[3]: Conter Control Register
+ */
+ devpriv->gpct_config[chan] = data[0];
+
+#if 0
+ /* Example of Counter Application */
+ /* One-shot (software trigger) */
+ cmReg.reg.coutSource = 0; /* out RCAP */
+ cmReg.reg.coutPolarity = 1; /* Polarity inverted */
+ cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */
+ cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */
+ cmReg.reg.ctEnableCtrl = 2; /* Hardware */
+ cmReg.reg.clockSource = 2; /* Internal */
+ cmReg.reg.countDir = 1; /* Down */
+ cmReg.reg.countDirCtrl = 1; /* Software */
+ cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */
+ cmReg.reg.preloadRegSel = 0; /* PR0 */
+ cmReg.reg.reserved = 0;
+
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ outw(0x0001, chan_iobase + REG_C0H);
+ outw(0x3C68, chan_iobase + REG_C0L);
+
+ /* Reset the counter */
+ outw(0x8000, chan_iobase + REG_C0C);
+ /* Load the counter from PR0 */
+ outw(0x4000, chan_iobase + REG_C0C);
+
+ /* Reset RCAP (fires one-shot) */
+ outw(0x0008, chan_iobase + REG_C0C);
+
+#endif
+
+#if 1
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Reset the counter if it is software preload */
+ if (cmReg.reg.autoLoadResetRcap == 0) {
+ /* Reset the counter */
+ outw(0x8000, chan_iobase + REG_C0C);
+ /* Load the counter from PR0
+ * outw(0x4000, chan_iobase + REG_C0C);
+ */
+ }
+#else
+ /* 0 quadrature, 1 software control */
+ cmReg.reg.countDirCtrl = 0;
+
+ /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
+ if (data[1] == GPCT_X2)
+ cmReg.reg.clockSource = 1;
+ else if (data[1] == GPCT_X4)
+ cmReg.reg.clockSource = 2;
+ else
+ cmReg.reg.clockSource = 0;
+
+ /* When to take into account the indexpulse: */
+ /*if (data[2] == GPCT_IndexPhaseLowLow) {
+ } else if (data[2] == GPCT_IndexPhaseLowHigh) {
+ } else if (data[2] == GPCT_IndexPhaseHighLow) {
+ } else if (data[2] == GPCT_IndexPhaseHighHigh) {
+ }*/
+ /* Take into account the index pulse? */
+ if (data[3] == GPCT_RESET_COUNTER_ON_INDEX)
+ /* Auto load with INDEX^ */
+ cmReg.reg.autoLoadResetRcap = 4;
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register high word */
+ val = (data[2] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register low word */
+ val = data[2] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Write the Counter Control Register */
+ if (data[3]) {
+ val = data[3] & 0xffff;
+ outw(val, chan_iobase + REG_C0C);
+ }
+ /* Reset the counter if it is software preload */
+ if (cmReg.reg.autoLoadResetRcap == 0) {
+ /* Reset the counter */
+ outw(0x8000, chan_iobase + REG_C0C);
+ /* Load the counter from PR0 */
+ outw(0x4000, chan_iobase + REG_C0C);
+ }
+#endif
+ break;
+
+ case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
+ /*
+ data[0]: Application Type
+ data[1]: Counter Mode Register Value
+ data[2]: Pre-load Register 0 Value
+ data[3]: Pre-load Register 1 Value
+ data[4]: Conter Control Register
+ */
+ devpriv->gpct_config[chan] = data[0];
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 0; /* PR0 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 0 high word */
+ val = (data[2] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 0 low word */
+ val = data[2] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 1; /* PR1 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 1 high word */
+ val = (data[3] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 1 low word */
+ val = data[3] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Write the Counter Control Register */
+ if (data[4]) {
+ val = data[4] & 0xffff;
+ outw(val, chan_iobase + REG_C0C);
+ }
+ break;
+
+ case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
+ /*
+ data[0]: Application Type
+ data[1]: Counter Mode Register Value
+ data[2]: Pre-load Register 0 Value
+ data[3]: Pre-load Register 1 Value
+ data[4]: Conter Control Register
+ */
+ devpriv->gpct_config[chan] = data[0];
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 0; /* PR0 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 0 high word */
+ val = (data[2] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 0 low word */
+ val = data[2] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 1; /* PR1 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 1 high word */
+ val = (data[3] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 1 low word */
+ val = data[3] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Write the Counter Control Register */
+ if (data[4]) {
+ val = data[4] & 0xffff;
+ outw(val, chan_iobase + REG_C0C);
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int s526_gpct_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long chan_iobase = dev->iobase + chan * 8;
+
+ inw(chan_iobase + REG_C0M); /* Is this read required? */
+
+ /* Check what Application of Counter this channel is configured for */
+ switch (devpriv->gpct_config[chan]) {
+ case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
+ /* data[0] contains the PULSE_WIDTH
+ data[1] contains the PULSE_PERIOD
+ @pre PULSE_PERIOD > PULSE_WIDTH > 0
+ The above periods must be expressed as a multiple of the
+ pulse frequency on the selected source
+ */
+ if ((data[1] <= data[0]) || !data[0])
+ return -EINVAL;
+
+ /* Fall thru to write the PULSE_WIDTH */
+
+ case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
+ case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
+ outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H);
+ outw(data[0] & 0xffff, chan_iobase + REG_C0L);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+#define ISR_ADC_DONE 0x4
+static int s526_ai_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct s526_private *devpriv = dev->private;
+ int result = -EINVAL;
+
+ if (insn->n < 1)
+ return result;
+
+ result = insn->n;
+
+ /* data[0] : channels was set in relevant bits.
+ data[1] : delay
+ */
+ /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to
+ * enable channels here. The channel should be enabled in the
+ * INSN_READ handler. */
+
+ /* Enable ADC interrupt */
+ outw(ISR_ADC_DONE, dev->iobase + REG_IER);
+ devpriv->ai_config = (data[0] & 0x3ff) << 5;
+ if (data[1] > 0)
+ devpriv->ai_config |= 0x8000; /* set the delay */
+
+ devpriv->ai_config |= 0x0001; /* ADC start bit */
+
+ return result;
+}
+
+static int s526_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + REG_ISR);
+ if (status & ISR_ADC_DONE)
+ return 0;
+ return -EBUSY;
+}
+
+static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int n;
+ unsigned short value;
+ unsigned int d;
+ int ret;
+
+ /* Set configured delay, enable channel for this channel only,
+ * select "ADC read" channel, set "ADC start" bit. */
+ value = (devpriv->ai_config & 0x8000) |
+ ((1 << 5) << chan) | (chan << 1) | 0x0001;
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion */
+ outw(value, dev->iobase + REG_ADC);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, s526_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ outw(ISR_ADC_DONE, dev->iobase + REG_ISR);
+
+ /* read data */
+ d = inw(dev->iobase + REG_ADD);
+
+ /* munge data */
+ data[n] = d ^ 0x8000;
+ }
+
+ /* return the number of samples read/written */
+ return n;
+}
+
+static int s526_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ int i;
+
+ outw(chan << 1, dev->iobase + REG_DAC);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, dev->iobase + REG_ADD);
+ /* starts the D/A conversion */
+ outw((chan << 1) | 1, dev->iobase + REG_DAC);
+ }
+ s->readback[chan] = val;
+
+ return insn->n;
+}
+
+static int s526_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + REG_DIO);
+
+ data[1] = inw(dev->iobase + REG_DIO) & 0xff;
+
+ return insn->n;
+}
+
+static int s526_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ /* bit 10/11 set the group 1/2's mode */
+ if (s->io_bits & 0x0f)
+ s->state |= (1 << 10);
+ else
+ s->state &= ~(1 << 10);
+ if (s->io_bits & 0xf0)
+ s->state |= (1 << 11);
+ else
+ s->state &= ~(1 << 11);
+
+ outw(s->state, dev->iobase + REG_DIO);
+
+ return insn->n;
+}
+
+static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct s526_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x40);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
+ s->n_chan = 4;
+ s->maxdata = 0x00ffffff; /* 24 bit counter */
+ s->insn_read = s526_gpct_rinsn;
+ s->insn_config = s526_gpct_insn_config;
+ s->insn_write = s526_gpct_winsn;
+
+ s = &dev->subdevices[1];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ /* channels 0 to 7 are the regular differential inputs */
+ /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */
+ s->n_chan = 10;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar10;
+ s->len_chanlist = 16;
+ s->insn_read = s526_ai_rinsn;
+ s->insn_config = s526_ai_insn_config;
+
+ s = &dev->subdevices[2];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = s526_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[3];
+ /* digital i/o subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = s526_dio_insn_bits;
+ s->insn_config = s526_dio_insn_config;
+
+ return 0;
+}
+
+static struct comedi_driver s526_driver = {
+ .driver_name = "s526",
+ .module = THIS_MODULE,
+ .attach = s526_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(s526_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/s626.c b/drivers/staging/comedi/drivers/s626.c
new file mode 100644
index 000000000..781918d8d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/s626.c
@@ -0,0 +1,2925 @@
+/*
+ * comedi/drivers/s626.c
+ * Sensoray s626 Comedi driver
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * Based on Sensoray Model 626 Linux driver Version 0.2
+ * Copyright (C) 2002-2004 Sensoray Co., Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: s626
+ * Description: Sensoray 626 driver
+ * Devices: [Sensoray] 626 (s626)
+ * Authors: Gianluca Palli <gpalli@deis.unibo.it>,
+ * Updated: Fri, 15 Feb 2008 10:28:42 +0000
+ * Status: experimental
+
+ * Configuration options: not applicable, uses PCI auto config
+
+ * INSN_CONFIG instructions:
+ * analog input:
+ * none
+ *
+ * analog output:
+ * none
+ *
+ * digital channel:
+ * s626 has 3 dio subdevices (2,3 and 4) each with 16 i/o channels
+ * supported configuration options:
+ * INSN_CONFIG_DIO_QUERY
+ * COMEDI_INPUT
+ * COMEDI_OUTPUT
+ *
+ * encoder:
+ * Every channel must be configured before reading.
+ *
+ * Example code
+ *
+ * insn.insn=INSN_CONFIG; //configuration instruction
+ * insn.n=1; //number of operation (must be 1)
+ * insn.data=&initialvalue; //initial value loaded into encoder
+ * //during configuration
+ * insn.subdev=5; //encoder subdevice
+ * insn.chanspec=CR_PACK(encoder_channel,0,AREF_OTHER); //encoder_channel
+ * //to configure
+ *
+ * comedi_do_insn(cf,&insn); //executing configuration
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#include "../comedi_pci.h"
+
+#include "s626.h"
+
+struct s626_buffer_dma {
+ dma_addr_t physical_base;
+ void *logical_base;
+};
+
+struct s626_private {
+ uint8_t ai_cmd_running; /* ai_cmd is running */
+ unsigned int ai_sample_timer; /* time between samples in
+ * units of the timer */
+ int ai_convert_count; /* conversion counter */
+ unsigned int ai_convert_timer; /* time between conversion in
+ * units of the timer */
+ uint16_t counter_int_enabs; /* counter interrupt enable mask
+ * for MISC2 register */
+ uint8_t adc_items; /* number of items in ADC poll list */
+ struct s626_buffer_dma rps_buf; /* DMA buffer used to hold ADC (RPS1)
+ * program */
+ struct s626_buffer_dma ana_buf; /* DMA buffer used to receive ADC data
+ * and hold DAC data */
+ uint32_t *dac_wbuf; /* pointer to logical adrs of DMA buffer
+ * used to hold DAC data */
+ uint16_t dacpol; /* image of DAC polarity register */
+ uint8_t trim_setpoint[12]; /* images of TrimDAC setpoints */
+ uint32_t i2c_adrs; /* I2C device address for onboard EEPROM
+ * (board rev dependent) */
+};
+
+/* Counter overflow/index event flag masks for RDMISC2. */
+#define S626_INDXMASK(C) (1 << (((C) > 2) ? ((C) * 2 - 1) : ((C) * 2 + 4)))
+#define S626_OVERMASK(C) (1 << (((C) > 2) ? ((C) * 2 + 5) : ((C) * 2 + 10)))
+
+/*
+ * Enable/disable a function or test status bit(s) that are accessed
+ * through Main Control Registers 1 or 2.
+ */
+static void s626_mc_enable(struct comedi_device *dev,
+ unsigned int cmd, unsigned int reg)
+{
+ unsigned int val = (cmd << 16) | cmd;
+
+ mmiowb();
+ writel(val, dev->mmio + reg);
+}
+
+static void s626_mc_disable(struct comedi_device *dev,
+ unsigned int cmd, unsigned int reg)
+{
+ writel(cmd << 16, dev->mmio + reg);
+ mmiowb();
+}
+
+static bool s626_mc_test(struct comedi_device *dev,
+ unsigned int cmd, unsigned int reg)
+{
+ unsigned int val;
+
+ val = readl(dev->mmio + reg);
+
+ return (val & cmd) ? true : false;
+}
+
+#define S626_BUGFIX_STREG(REGADRS) ((REGADRS) - 4)
+
+/* Write a time slot control record to TSL2. */
+#define S626_VECTPORT(VECTNUM) (S626_P_TSL2 + ((VECTNUM) << 2))
+
+static const struct comedi_lrange s626_range_table = {
+ 2, {
+ BIP_RANGE(5),
+ BIP_RANGE(10)
+ }
+};
+
+/*
+ * Execute a DEBI transfer. This must be called from within a critical section.
+ */
+static void s626_debi_transfer(struct comedi_device *dev)
+{
+ static const int timeout = 10000;
+ int i;
+
+ /* Initiate upload of shadow RAM to DEBI control register */
+ s626_mc_enable(dev, S626_MC2_UPLD_DEBI, S626_P_MC2);
+
+ /*
+ * Wait for completion of upload from shadow RAM to
+ * DEBI control register.
+ */
+ for (i = 0; i < timeout; i++) {
+ if (s626_mc_test(dev, S626_MC2_UPLD_DEBI, S626_P_MC2))
+ break;
+ udelay(1);
+ }
+ if (i == timeout)
+ dev_err(dev->class_dev,
+ "Timeout while uploading to DEBI control register\n");
+
+ /* Wait until DEBI transfer is done */
+ for (i = 0; i < timeout; i++) {
+ if (!(readl(dev->mmio + S626_P_PSR) & S626_PSR_DEBI_S))
+ break;
+ udelay(1);
+ }
+ if (i == timeout)
+ dev_err(dev->class_dev, "DEBI transfer timeout\n");
+}
+
+/*
+ * Read a value from a gate array register.
+ */
+static uint16_t s626_debi_read(struct comedi_device *dev, uint16_t addr)
+{
+ /* Set up DEBI control register value in shadow RAM */
+ writel(S626_DEBI_CMD_RDWORD | addr, dev->mmio + S626_P_DEBICMD);
+
+ /* Execute the DEBI transfer. */
+ s626_debi_transfer(dev);
+
+ return readl(dev->mmio + S626_P_DEBIAD);
+}
+
+/*
+ * Write a value to a gate array register.
+ */
+static void s626_debi_write(struct comedi_device *dev, uint16_t addr,
+ uint16_t wdata)
+{
+ /* Set up DEBI control register value in shadow RAM */
+ writel(S626_DEBI_CMD_WRWORD | addr, dev->mmio + S626_P_DEBICMD);
+ writel(wdata, dev->mmio + S626_P_DEBIAD);
+
+ /* Execute the DEBI transfer. */
+ s626_debi_transfer(dev);
+}
+
+/*
+ * Replace the specified bits in a gate array register. Imports: mask
+ * specifies bits that are to be preserved, wdata is new value to be
+ * or'd with the masked original.
+ */
+static void s626_debi_replace(struct comedi_device *dev, unsigned int addr,
+ unsigned int mask, unsigned int wdata)
+{
+ unsigned int val;
+
+ addr &= 0xffff;
+ writel(S626_DEBI_CMD_RDWORD | addr, dev->mmio + S626_P_DEBICMD);
+ s626_debi_transfer(dev);
+
+ writel(S626_DEBI_CMD_WRWORD | addr, dev->mmio + S626_P_DEBICMD);
+ val = readl(dev->mmio + S626_P_DEBIAD);
+ val &= mask;
+ val |= wdata;
+ writel(val & 0xffff, dev->mmio + S626_P_DEBIAD);
+ s626_debi_transfer(dev);
+}
+
+/* ************** EEPROM ACCESS FUNCTIONS ************** */
+
+static int s626_i2c_handshake_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ bool status;
+
+ status = s626_mc_test(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ if (status)
+ return 0;
+ return -EBUSY;
+}
+
+static int s626_i2c_handshake(struct comedi_device *dev, uint32_t val)
+{
+ unsigned int ctrl;
+ int ret;
+
+ /* Write I2C command to I2C Transfer Control shadow register */
+ writel(val, dev->mmio + S626_P_I2CCTRL);
+
+ /*
+ * Upload I2C shadow registers into working registers and
+ * wait for upload confirmation.
+ */
+ s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Wait until I2C bus transfer is finished or an error occurs */
+ do {
+ ctrl = readl(dev->mmio + S626_P_I2CCTRL);
+ } while ((ctrl & (S626_I2C_BUSY | S626_I2C_ERR)) == S626_I2C_BUSY);
+
+ /* Return non-zero if I2C error occurred */
+ return ctrl & S626_I2C_ERR;
+}
+
+/* Read uint8_t from EEPROM. */
+static uint8_t s626_i2c_read(struct comedi_device *dev, uint8_t addr)
+{
+ struct s626_private *devpriv = dev->private;
+
+ /*
+ * Send EEPROM target address:
+ * Byte2 = I2C command: write to I2C EEPROM device.
+ * Byte1 = EEPROM internal target address.
+ * Byte0 = Not sent.
+ */
+ if (s626_i2c_handshake(dev, S626_I2C_B2(S626_I2C_ATTRSTART,
+ devpriv->i2c_adrs) |
+ S626_I2C_B1(S626_I2C_ATTRSTOP, addr) |
+ S626_I2C_B0(S626_I2C_ATTRNOP, 0)))
+ /* Abort function and declare error if handshake failed. */
+ return 0;
+
+ /*
+ * Execute EEPROM read:
+ * Byte2 = I2C command: read from I2C EEPROM device.
+ * Byte1 receives uint8_t from EEPROM.
+ * Byte0 = Not sent.
+ */
+ if (s626_i2c_handshake(dev, S626_I2C_B2(S626_I2C_ATTRSTART,
+ (devpriv->i2c_adrs | 1)) |
+ S626_I2C_B1(S626_I2C_ATTRSTOP, 0) |
+ S626_I2C_B0(S626_I2C_ATTRNOP, 0)))
+ /* Abort function and declare error if handshake failed. */
+ return 0;
+
+ return (readl(dev->mmio + S626_P_I2CCTRL) >> 16) & 0xff;
+}
+
+/* *********** DAC FUNCTIONS *********** */
+
+/* TrimDac LogicalChan-to-PhysicalChan mapping table. */
+static const uint8_t s626_trimchan[] = { 10, 9, 8, 3, 2, 7, 6, 1, 0, 5, 4 };
+
+/* TrimDac LogicalChan-to-EepromAdrs mapping table. */
+static const uint8_t s626_trimadrs[] = {
+ 0x40, 0x41, 0x42, 0x50, 0x51, 0x52, 0x53, 0x60, 0x61, 0x62, 0x63
+};
+
+enum {
+ s626_send_dac_wait_not_mc1_a2out,
+ s626_send_dac_wait_ssr_af2_out,
+ s626_send_dac_wait_fb_buffer2_msb_00,
+ s626_send_dac_wait_fb_buffer2_msb_ff
+};
+
+static int s626_send_dac_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ switch (context) {
+ case s626_send_dac_wait_not_mc1_a2out:
+ status = readl(dev->mmio + S626_P_MC1);
+ if (!(status & S626_MC1_A2OUT))
+ return 0;
+ break;
+ case s626_send_dac_wait_ssr_af2_out:
+ status = readl(dev->mmio + S626_P_SSR);
+ if (status & S626_SSR_AF2_OUT)
+ return 0;
+ break;
+ case s626_send_dac_wait_fb_buffer2_msb_00:
+ status = readl(dev->mmio + S626_P_FB_BUFFER2);
+ if (!(status & 0xff000000))
+ return 0;
+ break;
+ case s626_send_dac_wait_fb_buffer2_msb_ff:
+ status = readl(dev->mmio + S626_P_FB_BUFFER2);
+ if (status & 0xff000000)
+ return 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return -EBUSY;
+}
+
+/*
+ * Private helper function: Transmit serial data to DAC via Audio
+ * channel 2. Assumes: (1) TSL2 slot records initialized, and (2)
+ * dacpol contains valid target image.
+ */
+static int s626_send_dac(struct comedi_device *dev, uint32_t val)
+{
+ struct s626_private *devpriv = dev->private;
+ int ret;
+
+ /* START THE SERIAL CLOCK RUNNING ------------- */
+
+ /*
+ * Assert DAC polarity control and enable gating of DAC serial clock
+ * and audio bit stream signals. At this point in time we must be
+ * assured of being in time slot 0. If we are not in slot 0, the
+ * serial clock and audio stream signals will be disabled; this is
+ * because the following s626_debi_write statement (which enables
+ * signals to be passed through the gate array) would execute before
+ * the trailing edge of WS1/WS3 (which turns off the signals), thus
+ * causing the signals to be inactive during the DAC write.
+ */
+ s626_debi_write(dev, S626_LP_DACPOL, devpriv->dacpol);
+
+ /* TRANSFER OUTPUT DWORD VALUE INTO A2'S OUTPUT FIFO ---------------- */
+
+ /* Copy DAC setpoint value to DAC's output DMA buffer. */
+ /* writel(val, dev->mmio + (uint32_t)devpriv->dac_wbuf); */
+ *devpriv->dac_wbuf = val;
+
+ /*
+ * Enable the output DMA transfer. This will cause the DMAC to copy
+ * the DAC's data value to A2's output FIFO. The DMA transfer will
+ * then immediately terminate because the protection address is
+ * reached upon transfer of the first DWORD value.
+ */
+ s626_mc_enable(dev, S626_MC1_A2OUT, S626_P_MC1);
+
+ /* While the DMA transfer is executing ... */
+
+ /*
+ * Reset Audio2 output FIFO's underflow flag (along with any
+ * other FIFO underflow/overflow flags). When set, this flag
+ * will indicate that we have emerged from slot 0.
+ */
+ writel(S626_ISR_AFOU, dev->mmio + S626_P_ISR);
+
+ /*
+ * Wait for the DMA transfer to finish so that there will be data
+ * available in the FIFO when time slot 1 tries to transfer a DWORD
+ * from the FIFO to the output buffer register. We test for DMA
+ * Done by polling the DMAC enable flag; this flag is automatically
+ * cleared when the transfer has finished.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_not_mc1_a2out);
+ if (ret) {
+ dev_err(dev->class_dev, "DMA transfer timeout\n");
+ return ret;
+ }
+
+ /* START THE OUTPUT STREAM TO THE TARGET DAC -------------------- */
+
+ /*
+ * FIFO data is now available, so we enable execution of time slots
+ * 1 and higher by clearing the EOS flag in slot 0. Note that SD3
+ * will be shifted in and stored in FB_BUFFER2 for end-of-slot-list
+ * detection.
+ */
+ writel(S626_XSD2 | S626_RSD3 | S626_SIB_A2,
+ dev->mmio + S626_VECTPORT(0));
+
+ /*
+ * Wait for slot 1 to execute to ensure that the Packet will be
+ * transmitted. This is detected by polling the Audio2 output FIFO
+ * underflow flag, which will be set when slot 1 execution has
+ * finished transferring the DAC's data DWORD from the output FIFO
+ * to the output buffer register.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_ssr_af2_out);
+ if (ret) {
+ dev_err(dev->class_dev,
+ "TSL timeout waiting for slot 1 to execute\n");
+ return ret;
+ }
+
+ /*
+ * Set up to trap execution at slot 0 when the TSL sequencer cycles
+ * back to slot 0 after executing the EOS in slot 5. Also,
+ * simultaneously shift out and in the 0x00 that is ALWAYS the value
+ * stored in the last byte to be shifted out of the FIFO's DWORD
+ * buffer register.
+ */
+ writel(S626_XSD2 | S626_XFIFO_2 | S626_RSD2 | S626_SIB_A2 | S626_EOS,
+ dev->mmio + S626_VECTPORT(0));
+
+ /* WAIT FOR THE TRANSACTION TO FINISH ----------------------- */
+
+ /*
+ * Wait for the TSL to finish executing all time slots before
+ * exiting this function. We must do this so that the next DAC
+ * write doesn't start, thereby enabling clock/chip select signals:
+ *
+ * 1. Before the TSL sequence cycles back to slot 0, which disables
+ * the clock/cs signal gating and traps slot // list execution.
+ * we have not yet finished slot 5 then the clock/cs signals are
+ * still gated and we have not finished transmitting the stream.
+ *
+ * 2. While slots 2-5 are executing due to a late slot 0 trap. In
+ * this case, the slot sequence is currently repeating, but with
+ * clock/cs signals disabled. We must wait for slot 0 to trap
+ * execution before setting up the next DAC setpoint DMA transfer
+ * and enabling the clock/cs signals. To detect the end of slot 5,
+ * we test for the FB_BUFFER2 MSB contents to be equal to 0xFF. If
+ * the TSL has not yet finished executing slot 5 ...
+ */
+ if (readl(dev->mmio + S626_P_FB_BUFFER2) & 0xff000000) {
+ /*
+ * The trap was set on time and we are still executing somewhere
+ * in slots 2-5, so we now wait for slot 0 to execute and trap
+ * TSL execution. This is detected when FB_BUFFER2 MSB changes
+ * from 0xFF to 0x00, which slot 0 causes to happen by shifting
+ * out/in on SD2 the 0x00 that is always referenced by slot 5.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_fb_buffer2_msb_00);
+ if (ret) {
+ dev_err(dev->class_dev,
+ "TSL timeout waiting for slot 0 to execute\n");
+ return ret;
+ }
+ }
+ /*
+ * Either (1) we were too late setting the slot 0 trap; the TSL
+ * sequencer restarted slot 0 before we could set the EOS trap flag,
+ * or (2) we were not late and execution is now trapped at slot 0.
+ * In either case, we must now change slot 0 so that it will store
+ * value 0xFF (instead of 0x00) to FB_BUFFER2 next time it executes.
+ * In order to do this, we reprogram slot 0 so that it will shift in
+ * SD3, which is driven only by a pull-up resistor.
+ */
+ writel(S626_RSD3 | S626_SIB_A2 | S626_EOS,
+ dev->mmio + S626_VECTPORT(0));
+
+ /*
+ * Wait for slot 0 to execute, at which time the TSL is setup for
+ * the next DAC write. This is detected when FB_BUFFER2 MSB changes
+ * from 0x00 to 0xFF.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_fb_buffer2_msb_ff);
+ if (ret) {
+ dev_err(dev->class_dev,
+ "TSL timeout waiting for slot 0 to execute\n");
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Private helper function: Write setpoint to an application DAC channel.
+ */
+static int s626_set_dac(struct comedi_device *dev,
+ uint16_t chan, int16_t dacdata)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t signmask;
+ uint32_t ws_image;
+ uint32_t val;
+
+ /*
+ * Adjust DAC data polarity and set up Polarity Control Register image.
+ */
+ signmask = 1 << chan;
+ if (dacdata < 0) {
+ dacdata = -dacdata;
+ devpriv->dacpol |= signmask;
+ } else {
+ devpriv->dacpol &= ~signmask;
+ }
+
+ /* Limit DAC setpoint value to valid range. */
+ if ((uint16_t)dacdata > 0x1FFF)
+ dacdata = 0x1FFF;
+
+ /*
+ * Set up TSL2 records (aka "vectors") for DAC update. Vectors V2
+ * and V3 transmit the setpoint to the target DAC. V4 and V5 send
+ * data to a non-existent TrimDac channel just to keep the clock
+ * running after sending data to the target DAC. This is necessary
+ * to eliminate the clock glitch that would otherwise occur at the
+ * end of the target DAC's serial data stream. When the sequence
+ * restarts at V0 (after executing V5), the gate array automatically
+ * disables gating for the DAC clock and all DAC chip selects.
+ */
+
+ /* Choose DAC chip select to be asserted */
+ ws_image = (chan & 2) ? S626_WS1 : S626_WS2;
+ /* Slot 2: Transmit high data byte to target DAC */
+ writel(S626_XSD2 | S626_XFIFO_1 | ws_image,
+ dev->mmio + S626_VECTPORT(2));
+ /* Slot 3: Transmit low data byte to target DAC */
+ writel(S626_XSD2 | S626_XFIFO_0 | ws_image,
+ dev->mmio + S626_VECTPORT(3));
+ /* Slot 4: Transmit to non-existent TrimDac channel to keep clock */
+ writel(S626_XSD2 | S626_XFIFO_3 | S626_WS3,
+ dev->mmio + S626_VECTPORT(4));
+ /* Slot 5: running after writing target DAC's low data byte */
+ writel(S626_XSD2 | S626_XFIFO_2 | S626_WS3 | S626_EOS,
+ dev->mmio + S626_VECTPORT(5));
+
+ /*
+ * Construct and transmit target DAC's serial packet:
+ * (A10D DDDD), (DDDD DDDD), (0x0F), (0x00) where A is chan<0>,
+ * and D<12:0> is the DAC setpoint. Append a WORD value (that writes
+ * to a non-existent TrimDac channel) that serves to keep the clock
+ * running after the packet has been sent to the target DAC.
+ */
+ val = 0x0F000000; /* Continue clock after target DAC data
+ * (write to non-existent trimdac). */
+ val |= 0x00004000; /* Address the two main dual-DAC devices
+ * (TSL's chip select enables target device). */
+ val |= ((uint32_t)(chan & 1) << 15); /* Address the DAC channel
+ * within the device. */
+ val |= (uint32_t)dacdata; /* Include DAC setpoint data. */
+ return s626_send_dac(dev, val);
+}
+
+static int s626_write_trim_dac(struct comedi_device *dev,
+ uint8_t logical_chan, uint8_t dac_data)
+{
+ struct s626_private *devpriv = dev->private;
+ uint32_t chan;
+
+ /*
+ * Save the new setpoint in case the application needs to read it back
+ * later.
+ */
+ devpriv->trim_setpoint[logical_chan] = (uint8_t)dac_data;
+
+ /* Map logical channel number to physical channel number. */
+ chan = s626_trimchan[logical_chan];
+
+ /*
+ * Set up TSL2 records for TrimDac write operation. All slots shift
+ * 0xFF in from pulled-up SD3 so that the end of the slot sequence
+ * can be detected.
+ */
+
+ /* Slot 2: Send high uint8_t to target TrimDac */
+ writel(S626_XSD2 | S626_XFIFO_1 | S626_WS3,
+ dev->mmio + S626_VECTPORT(2));
+ /* Slot 3: Send low uint8_t to target TrimDac */
+ writel(S626_XSD2 | S626_XFIFO_0 | S626_WS3,
+ dev->mmio + S626_VECTPORT(3));
+ /* Slot 4: Send NOP high uint8_t to DAC0 to keep clock running */
+ writel(S626_XSD2 | S626_XFIFO_3 | S626_WS1,
+ dev->mmio + S626_VECTPORT(4));
+ /* Slot 5: Send NOP low uint8_t to DAC0 */
+ writel(S626_XSD2 | S626_XFIFO_2 | S626_WS1 | S626_EOS,
+ dev->mmio + S626_VECTPORT(5));
+
+ /*
+ * Construct and transmit target DAC's serial packet:
+ * (0000 AAAA), (DDDD DDDD), (0x00), (0x00) where A<3:0> is the
+ * DAC channel's address, and D<7:0> is the DAC setpoint. Append a
+ * WORD value (that writes a channel 0 NOP command to a non-existent
+ * main DAC channel) that serves to keep the clock running after the
+ * packet has been sent to the target DAC.
+ */
+
+ /*
+ * Address the DAC channel within the trimdac device.
+ * Include DAC setpoint data.
+ */
+ return s626_send_dac(dev, (chan << 8) | dac_data);
+}
+
+static int s626_load_trim_dacs(struct comedi_device *dev)
+{
+ uint8_t i;
+ int ret;
+
+ /* Copy TrimDac setpoint values from EEPROM to TrimDacs. */
+ for (i = 0; i < ARRAY_SIZE(s626_trimchan); i++) {
+ ret = s626_write_trim_dac(dev, i,
+ s626_i2c_read(dev, s626_trimadrs[i]));
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/* ****** COUNTER FUNCTIONS ******* */
+
+/*
+ * All counter functions address a specific counter by means of the
+ * "Counter" argument, which is a logical counter number. The Counter
+ * argument may have any of the following legal values: 0=0A, 1=1A,
+ * 2=2A, 3=0B, 4=1B, 5=2B.
+ */
+
+/*
+ * Return/set a counter pair's latch trigger source. 0: On read
+ * access, 1: A index latches A, 2: B index latches B, 3: A overflow
+ * latches B.
+ */
+static void s626_set_latch_source(struct comedi_device *dev,
+ unsigned int chan, uint16_t value)
+{
+ s626_debi_replace(dev, S626_LP_CRB(chan),
+ ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_LATCHSRC),
+ S626_SET_CRB_LATCHSRC(value));
+}
+
+/*
+ * Write value into counter preload register.
+ */
+static void s626_preload(struct comedi_device *dev,
+ unsigned int chan, uint32_t value)
+{
+ s626_debi_write(dev, S626_LP_CNTR(chan), value);
+ s626_debi_write(dev, S626_LP_CNTR(chan) + 2, value >> 16);
+}
+
+/* ****** PRIVATE COUNTER FUNCTIONS ****** */
+
+/*
+ * Reset a counter's index and overflow event capture flags.
+ */
+static void s626_reset_cap_flags(struct comedi_device *dev,
+ unsigned int chan)
+{
+ uint16_t set;
+
+ set = S626_SET_CRB_INTRESETCMD(1);
+ if (chan < 3)
+ set |= S626_SET_CRB_INTRESET_A(1);
+ else
+ set |= S626_SET_CRB_INTRESET_B(1);
+
+ s626_debi_replace(dev, S626_LP_CRB(chan), ~S626_CRBMSK_INTCTRL, set);
+}
+
+#ifdef unused
+/*
+ * Return counter setup in a format (COUNTER_SETUP) that is consistent
+ * for both A and B counters.
+ */
+static uint16_t s626_get_mode_a(struct comedi_device *dev,
+ unsigned int chan)
+{
+ uint16_t cra;
+ uint16_t crb;
+ uint16_t setup;
+ unsigned cntsrc, clkmult, clkpol, encmode;
+
+ /* Fetch CRA and CRB register images. */
+ cra = s626_debi_read(dev, S626_LP_CRA(chan));
+ crb = s626_debi_read(dev, S626_LP_CRB(chan));
+
+ /*
+ * Populate the standardized counter setup bit fields.
+ */
+ setup =
+ /* LoadSrc = LoadSrcA. */
+ S626_SET_STD_LOADSRC(S626_GET_CRA_LOADSRC_A(cra)) |
+ /* LatchSrc = LatchSrcA. */
+ S626_SET_STD_LATCHSRC(S626_GET_CRB_LATCHSRC(crb)) |
+ /* IntSrc = IntSrcA. */
+ S626_SET_STD_INTSRC(S626_GET_CRA_INTSRC_A(cra)) |
+ /* IndxSrc = IndxSrcA. */
+ S626_SET_STD_INDXSRC(S626_GET_CRA_INDXSRC_A(cra)) |
+ /* IndxPol = IndxPolA. */
+ S626_SET_STD_INDXPOL(S626_GET_CRA_INDXPOL_A(cra)) |
+ /* ClkEnab = ClkEnabA. */
+ S626_SET_STD_CLKENAB(S626_GET_CRB_CLKENAB_A(crb));
+
+ /* Adjust mode-dependent parameters. */
+ cntsrc = S626_GET_CRA_CNTSRC_A(cra);
+ if (cntsrc & S626_CNTSRC_SYSCLK) {
+ /* Timer mode (CntSrcA<1> == 1): */
+ encmode = S626_ENCMODE_TIMER;
+ /* Set ClkPol to indicate count direction (CntSrcA<0>). */
+ clkpol = cntsrc & 1;
+ /* ClkMult must be 1x in Timer mode. */
+ clkmult = S626_CLKMULT_1X;
+ } else {
+ /* Counter mode (CntSrcA<1> == 0): */
+ encmode = S626_ENCMODE_COUNTER;
+ /* Pass through ClkPol. */
+ clkpol = S626_GET_CRA_CLKPOL_A(cra);
+ /* Force ClkMult to 1x if not legal, else pass through. */
+ clkmult = S626_GET_CRA_CLKMULT_A(cra);
+ if (clkmult == S626_CLKMULT_SPECIAL)
+ clkmult = S626_CLKMULT_1X;
+ }
+ setup |= S626_SET_STD_ENCMODE(encmode) | S626_SET_STD_CLKMULT(clkmult) |
+ S626_SET_STD_CLKPOL(clkpol);
+
+ /* Return adjusted counter setup. */
+ return setup;
+}
+
+static uint16_t s626_get_mode_b(struct comedi_device *dev,
+ unsigned int chan)
+{
+ uint16_t cra;
+ uint16_t crb;
+ uint16_t setup;
+ unsigned cntsrc, clkmult, clkpol, encmode;
+
+ /* Fetch CRA and CRB register images. */
+ cra = s626_debi_read(dev, S626_LP_CRA(chan));
+ crb = s626_debi_read(dev, S626_LP_CRB(chan));
+
+ /*
+ * Populate the standardized counter setup bit fields.
+ */
+ setup =
+ /* IntSrc = IntSrcB. */
+ S626_SET_STD_INTSRC(S626_GET_CRB_INTSRC_B(crb)) |
+ /* LatchSrc = LatchSrcB. */
+ S626_SET_STD_LATCHSRC(S626_GET_CRB_LATCHSRC(crb)) |
+ /* LoadSrc = LoadSrcB. */
+ S626_SET_STD_LOADSRC(S626_GET_CRB_LOADSRC_B(crb)) |
+ /* IndxPol = IndxPolB. */
+ S626_SET_STD_INDXPOL(S626_GET_CRB_INDXPOL_B(crb)) |
+ /* ClkEnab = ClkEnabB. */
+ S626_SET_STD_CLKENAB(S626_GET_CRB_CLKENAB_B(crb)) |
+ /* IndxSrc = IndxSrcB. */
+ S626_SET_STD_INDXSRC(S626_GET_CRA_INDXSRC_B(cra));
+
+ /* Adjust mode-dependent parameters. */
+ cntsrc = S626_GET_CRA_CNTSRC_B(cra);
+ clkmult = S626_GET_CRB_CLKMULT_B(crb);
+ if (clkmult == S626_CLKMULT_SPECIAL) {
+ /* Extender mode (ClkMultB == S626_CLKMULT_SPECIAL): */
+ encmode = S626_ENCMODE_EXTENDER;
+ /* Indicate multiplier is 1x. */
+ clkmult = S626_CLKMULT_1X;
+ /* Set ClkPol equal to Timer count direction (CntSrcB<0>). */
+ clkpol = cntsrc & 1;
+ } else if (cntsrc & S626_CNTSRC_SYSCLK) {
+ /* Timer mode (CntSrcB<1> == 1): */
+ encmode = S626_ENCMODE_TIMER;
+ /* Indicate multiplier is 1x. */
+ clkmult = S626_CLKMULT_1X;
+ /* Set ClkPol equal to Timer count direction (CntSrcB<0>). */
+ clkpol = cntsrc & 1;
+ } else {
+ /* If Counter mode (CntSrcB<1> == 0): */
+ encmode = S626_ENCMODE_COUNTER;
+ /* Clock multiplier is passed through. */
+ /* Clock polarity is passed through. */
+ clkpol = S626_GET_CRB_CLKPOL_B(crb);
+ }
+ setup |= S626_SET_STD_ENCMODE(encmode) | S626_SET_STD_CLKMULT(clkmult) |
+ S626_SET_STD_CLKPOL(clkpol);
+
+ /* Return adjusted counter setup. */
+ return setup;
+}
+
+static uint16_t s626_get_mode(struct comedi_device *dev,
+ unsigned int chan)
+{
+ return (chan < 3) ? s626_get_mode_a(dev, chan)
+ : s626_get_mode_b(dev, chan);
+}
+#endif
+
+/*
+ * Set the operating mode for the specified counter. The setup
+ * parameter is treated as a COUNTER_SETUP data type. The following
+ * parameters are programmable (all other parms are ignored): ClkMult,
+ * ClkPol, ClkEnab, IndexSrc, IndexPol, LoadSrc.
+ */
+static void s626_set_mode_a(struct comedi_device *dev,
+ unsigned int chan, uint16_t setup,
+ uint16_t disable_int_src)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t cra;
+ uint16_t crb;
+ unsigned cntsrc, clkmult, clkpol;
+
+ /* Initialize CRA and CRB images. */
+ /* Preload trigger is passed through. */
+ cra = S626_SET_CRA_LOADSRC_A(S626_GET_STD_LOADSRC(setup));
+ /* IndexSrc is passed through. */
+ cra |= S626_SET_CRA_INDXSRC_A(S626_GET_STD_INDXSRC(setup));
+
+ /* Reset any pending CounterA event captures. */
+ crb = S626_SET_CRB_INTRESETCMD(1) | S626_SET_CRB_INTRESET_A(1);
+ /* Clock enable is passed through. */
+ crb |= S626_SET_CRB_CLKENAB_A(S626_GET_STD_CLKENAB(setup));
+
+ /* Force IntSrc to Disabled if disable_int_src is asserted. */
+ if (!disable_int_src)
+ cra |= S626_SET_CRA_INTSRC_A(S626_GET_STD_INTSRC(setup));
+
+ /* Populate all mode-dependent attributes of CRA & CRB images. */
+ clkpol = S626_GET_STD_CLKPOL(setup);
+ switch (S626_GET_STD_ENCMODE(setup)) {
+ case S626_ENCMODE_EXTENDER: /* Extender Mode: */
+ /* Force to Timer mode (Extender valid only for B counters). */
+ /* Fall through to case S626_ENCMODE_TIMER: */
+ case S626_ENCMODE_TIMER: /* Timer Mode: */
+ /* CntSrcA<1> selects system clock */
+ cntsrc = S626_CNTSRC_SYSCLK;
+ /* Count direction (CntSrcA<0>) obtained from ClkPol. */
+ cntsrc |= clkpol;
+ /* ClkPolA behaves as always-on clock enable. */
+ clkpol = 1;
+ /* ClkMult must be 1x. */
+ clkmult = S626_CLKMULT_1X;
+ break;
+ default: /* Counter Mode: */
+ /* Select ENC_C and ENC_D as clock/direction inputs. */
+ cntsrc = S626_CNTSRC_ENCODER;
+ /* Clock polarity is passed through. */
+ /* Force multiplier to x1 if not legal, else pass through. */
+ clkmult = S626_GET_STD_CLKMULT(setup);
+ if (clkmult == S626_CLKMULT_SPECIAL)
+ clkmult = S626_CLKMULT_1X;
+ break;
+ }
+ cra |= S626_SET_CRA_CNTSRC_A(cntsrc) | S626_SET_CRA_CLKPOL_A(clkpol) |
+ S626_SET_CRA_CLKMULT_A(clkmult);
+
+ /*
+ * Force positive index polarity if IndxSrc is software-driven only,
+ * otherwise pass it through.
+ */
+ if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT)
+ cra |= S626_SET_CRA_INDXPOL_A(S626_GET_STD_INDXPOL(setup));
+
+ /*
+ * If IntSrc has been forced to Disabled, update the MISC2 interrupt
+ * enable mask to indicate the counter interrupt is disabled.
+ */
+ if (disable_int_src)
+ devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) |
+ S626_INDXMASK(chan));
+
+ /*
+ * While retaining CounterB and LatchSrc configurations, program the
+ * new counter operating mode.
+ */
+ s626_debi_replace(dev, S626_LP_CRA(chan),
+ S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B, cra);
+ s626_debi_replace(dev, S626_LP_CRB(chan),
+ ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_A), crb);
+}
+
+static void s626_set_mode_b(struct comedi_device *dev,
+ unsigned int chan, uint16_t setup,
+ uint16_t disable_int_src)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t cra;
+ uint16_t crb;
+ unsigned cntsrc, clkmult, clkpol;
+
+ /* Initialize CRA and CRB images. */
+ /* IndexSrc is passed through. */
+ cra = S626_SET_CRA_INDXSRC_B(S626_GET_STD_INDXSRC(setup));
+
+ /* Reset event captures and disable interrupts. */
+ crb = S626_SET_CRB_INTRESETCMD(1) | S626_SET_CRB_INTRESET_B(1);
+ /* Clock enable is passed through. */
+ crb |= S626_SET_CRB_CLKENAB_B(S626_GET_STD_CLKENAB(setup));
+ /* Preload trigger source is passed through. */
+ crb |= S626_SET_CRB_LOADSRC_B(S626_GET_STD_LOADSRC(setup));
+
+ /* Force IntSrc to Disabled if disable_int_src is asserted. */
+ if (!disable_int_src)
+ crb |= S626_SET_CRB_INTSRC_B(S626_GET_STD_INTSRC(setup));
+
+ /* Populate all mode-dependent attributes of CRA & CRB images. */
+ clkpol = S626_GET_STD_CLKPOL(setup);
+ switch (S626_GET_STD_ENCMODE(setup)) {
+ case S626_ENCMODE_TIMER: /* Timer Mode: */
+ /* CntSrcB<1> selects system clock */
+ cntsrc = S626_CNTSRC_SYSCLK;
+ /* with direction (CntSrcB<0>) obtained from ClkPol. */
+ cntsrc |= clkpol;
+ /* ClkPolB behaves as always-on clock enable. */
+ clkpol = 1;
+ /* ClkMultB must be 1x. */
+ clkmult = S626_CLKMULT_1X;
+ break;
+ case S626_ENCMODE_EXTENDER: /* Extender Mode: */
+ /* CntSrcB source is OverflowA (same as "timer") */
+ cntsrc = S626_CNTSRC_SYSCLK;
+ /* with direction obtained from ClkPol. */
+ cntsrc |= clkpol;
+ /* ClkPolB controls IndexB -- always set to active. */
+ clkpol = 1;
+ /* ClkMultB selects OverflowA as the clock source. */
+ clkmult = S626_CLKMULT_SPECIAL;
+ break;
+ default: /* Counter Mode: */
+ /* Select ENC_C and ENC_D as clock/direction inputs. */
+ cntsrc = S626_CNTSRC_ENCODER;
+ /* ClkPol is passed through. */
+ /* Force ClkMult to x1 if not legal, otherwise pass through. */
+ clkmult = S626_GET_STD_CLKMULT(setup);
+ if (clkmult == S626_CLKMULT_SPECIAL)
+ clkmult = S626_CLKMULT_1X;
+ break;
+ }
+ cra |= S626_SET_CRA_CNTSRC_B(cntsrc);
+ crb |= S626_SET_CRB_CLKPOL_B(clkpol) | S626_SET_CRB_CLKMULT_B(clkmult);
+
+ /*
+ * Force positive index polarity if IndxSrc is software-driven only,
+ * otherwise pass it through.
+ */
+ if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT)
+ crb |= S626_SET_CRB_INDXPOL_B(S626_GET_STD_INDXPOL(setup));
+
+ /*
+ * If IntSrc has been forced to Disabled, update the MISC2 interrupt
+ * enable mask to indicate the counter interrupt is disabled.
+ */
+ if (disable_int_src)
+ devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) |
+ S626_INDXMASK(chan));
+
+ /*
+ * While retaining CounterA and LatchSrc configurations, program the
+ * new counter operating mode.
+ */
+ s626_debi_replace(dev, S626_LP_CRA(chan),
+ ~(S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B), cra);
+ s626_debi_replace(dev, S626_LP_CRB(chan),
+ S626_CRBMSK_CLKENAB_A | S626_CRBMSK_LATCHSRC, crb);
+}
+
+static void s626_set_mode(struct comedi_device *dev,
+ unsigned int chan,
+ uint16_t setup, uint16_t disable_int_src)
+{
+ if (chan < 3)
+ s626_set_mode_a(dev, chan, setup, disable_int_src);
+ else
+ s626_set_mode_b(dev, chan, setup, disable_int_src);
+}
+
+/*
+ * Return/set a counter's enable. enab: 0=always enabled, 1=enabled by index.
+ */
+static void s626_set_enable(struct comedi_device *dev,
+ unsigned int chan, uint16_t enab)
+{
+ unsigned int mask = S626_CRBMSK_INTCTRL;
+ unsigned int set;
+
+ if (chan < 3) {
+ mask |= S626_CRBMSK_CLKENAB_A;
+ set = S626_SET_CRB_CLKENAB_A(enab);
+ } else {
+ mask |= S626_CRBMSK_CLKENAB_B;
+ set = S626_SET_CRB_CLKENAB_B(enab);
+ }
+ s626_debi_replace(dev, S626_LP_CRB(chan), ~mask, set);
+}
+
+#ifdef unused
+static uint16_t s626_get_enable(struct comedi_device *dev,
+ unsigned int chan)
+{
+ uint16_t crb = s626_debi_read(dev, S626_LP_CRB(chan));
+
+ return (chan < 3) ? S626_GET_CRB_CLKENAB_A(crb)
+ : S626_GET_CRB_CLKENAB_B(crb);
+}
+#endif
+
+#ifdef unused
+static uint16_t s626_get_latch_source(struct comedi_device *dev,
+ unsigned int chan)
+{
+ return S626_GET_CRB_LATCHSRC(s626_debi_read(dev, S626_LP_CRB(chan)));
+}
+#endif
+
+/*
+ * Return/set the event that will trigger transfer of the preload
+ * register into the counter. 0=ThisCntr_Index, 1=ThisCntr_Overflow,
+ * 2=OverflowA (B counters only), 3=disabled.
+ */
+static void s626_set_load_trig(struct comedi_device *dev,
+ unsigned int chan, uint16_t trig)
+{
+ uint16_t reg;
+ uint16_t mask;
+ uint16_t set;
+
+ if (chan < 3) {
+ reg = S626_LP_CRA(chan);
+ mask = S626_CRAMSK_LOADSRC_A;
+ set = S626_SET_CRA_LOADSRC_A(trig);
+ } else {
+ reg = S626_LP_CRB(chan);
+ mask = S626_CRBMSK_LOADSRC_B | S626_CRBMSK_INTCTRL;
+ set = S626_SET_CRB_LOADSRC_B(trig);
+ }
+ s626_debi_replace(dev, reg, ~mask, set);
+}
+
+#ifdef unused
+static uint16_t s626_get_load_trig(struct comedi_device *dev,
+ unsigned int chan)
+{
+ if (chan < 3)
+ return S626_GET_CRA_LOADSRC_A(s626_debi_read(dev,
+ S626_LP_CRA(chan)));
+ else
+ return S626_GET_CRB_LOADSRC_B(s626_debi_read(dev,
+ S626_LP_CRB(chan)));
+}
+#endif
+
+/*
+ * Return/set counter interrupt source and clear any captured
+ * index/overflow events. int_source: 0=Disabled, 1=OverflowOnly,
+ * 2=IndexOnly, 3=IndexAndOverflow.
+ */
+static void s626_set_int_src(struct comedi_device *dev,
+ unsigned int chan, uint16_t int_source)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t cra_reg = S626_LP_CRA(chan);
+ uint16_t crb_reg = S626_LP_CRB(chan);
+
+ if (chan < 3) {
+ /* Reset any pending counter overflow or index captures */
+ s626_debi_replace(dev, crb_reg, ~S626_CRBMSK_INTCTRL,
+ S626_SET_CRB_INTRESETCMD(1) |
+ S626_SET_CRB_INTRESET_A(1));
+
+ /* Program counter interrupt source */
+ s626_debi_replace(dev, cra_reg, ~S626_CRAMSK_INTSRC_A,
+ S626_SET_CRA_INTSRC_A(int_source));
+ } else {
+ uint16_t crb;
+
+ /* Cache writeable CRB register image */
+ crb = s626_debi_read(dev, crb_reg);
+ crb &= ~S626_CRBMSK_INTCTRL;
+
+ /* Reset any pending counter overflow or index captures */
+ s626_debi_write(dev, crb_reg,
+ crb | S626_SET_CRB_INTRESETCMD(1) |
+ S626_SET_CRB_INTRESET_B(1));
+
+ /* Program counter interrupt source */
+ s626_debi_write(dev, crb_reg,
+ (crb & ~S626_CRBMSK_INTSRC_B) |
+ S626_SET_CRB_INTSRC_B(int_source));
+ }
+
+ /* Update MISC2 interrupt enable mask. */
+ devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) |
+ S626_INDXMASK(chan));
+ switch (int_source) {
+ case 0:
+ default:
+ break;
+ case 1:
+ devpriv->counter_int_enabs |= S626_OVERMASK(chan);
+ break;
+ case 2:
+ devpriv->counter_int_enabs |= S626_INDXMASK(chan);
+ break;
+ case 3:
+ devpriv->counter_int_enabs |= (S626_OVERMASK(chan) |
+ S626_INDXMASK(chan));
+ break;
+ }
+}
+
+#ifdef unused
+static uint16_t s626_get_int_src(struct comedi_device *dev,
+ unsigned int chan)
+{
+ if (chan < 3)
+ return S626_GET_CRA_INTSRC_A(s626_debi_read(dev,
+ S626_LP_CRA(chan)));
+ else
+ return S626_GET_CRB_INTSRC_B(s626_debi_read(dev,
+ S626_LP_CRB(chan)));
+}
+#endif
+
+#ifdef unused
+/*
+ * Return/set the clock multiplier.
+ */
+static void s626_set_clk_mult(struct comedi_device *dev,
+ unsigned int chan, uint16_t value)
+{
+ uint16_t mode;
+
+ mode = s626_get_mode(dev, chan);
+ mode &= ~S626_STDMSK_CLKMULT;
+ mode |= S626_SET_STD_CLKMULT(value);
+
+ s626_set_mode(dev, chan, mode, false);
+}
+
+static uint16_t s626_get_clk_mult(struct comedi_device *dev,
+ unsigned int chan)
+{
+ return S626_GET_STD_CLKMULT(s626_get_mode(dev, chan));
+}
+
+/*
+ * Return/set the clock polarity.
+ */
+static void s626_set_clk_pol(struct comedi_device *dev,
+ unsigned int chan, uint16_t value)
+{
+ uint16_t mode;
+
+ mode = s626_get_mode(dev, chan);
+ mode &= ~S626_STDMSK_CLKPOL;
+ mode |= S626_SET_STD_CLKPOL(value);
+
+ s626_set_mode(dev, chan, mode, false);
+}
+
+static uint16_t s626_get_clk_pol(struct comedi_device *dev,
+ unsigned int chan)
+{
+ return S626_GET_STD_CLKPOL(s626_get_mode(dev, chan));
+}
+
+/*
+ * Return/set the encoder mode.
+ */
+static void s626_set_enc_mode(struct comedi_device *dev,
+ unsigned int chan, uint16_t value)
+{
+ uint16_t mode;
+
+ mode = s626_get_mode(dev, chan);
+ mode &= ~S626_STDMSK_ENCMODE;
+ mode |= S626_SET_STD_ENCMODE(value);
+
+ s626_set_mode(dev, chan, mode, false);
+}
+
+static uint16_t s626_get_enc_mode(struct comedi_device *dev,
+ unsigned int chan)
+{
+ return S626_GET_STD_ENCMODE(s626_get_mode(dev, chan));
+}
+
+/*
+ * Return/set the index polarity.
+ */
+static void s626_set_index_pol(struct comedi_device *dev,
+ unsigned int chan, uint16_t value)
+{
+ uint16_t mode;
+
+ mode = s626_get_mode(dev, chan);
+ mode &= ~S626_STDMSK_INDXPOL;
+ mode |= S626_SET_STD_INDXPOL(value != 0);
+
+ s626_set_mode(dev, chan, mode, false);
+}
+
+static uint16_t s626_get_index_pol(struct comedi_device *dev,
+ unsigned int chan)
+{
+ return S626_GET_STD_INDXPOL(s626_get_mode(dev, chan));
+}
+
+/*
+ * Return/set the index source.
+ */
+static void s626_set_index_src(struct comedi_device *dev,
+ unsigned int chan, uint16_t value)
+{
+ uint16_t mode;
+
+ mode = s626_get_mode(dev, chan);
+ mode &= ~S626_STDMSK_INDXSRC;
+ mode |= S626_SET_STD_INDXSRC(value != 0);
+
+ s626_set_mode(dev, chan, mode, false);
+}
+
+static uint16_t s626_get_index_src(struct comedi_device *dev,
+ unsigned int chan)
+{
+ return S626_GET_STD_INDXSRC(s626_get_mode(dev, chan));
+}
+#endif
+
+/*
+ * Generate an index pulse.
+ */
+static void s626_pulse_index(struct comedi_device *dev,
+ unsigned int chan)
+{
+ if (chan < 3) {
+ uint16_t cra;
+
+ cra = s626_debi_read(dev, S626_LP_CRA(chan));
+
+ /* Pulse index */
+ s626_debi_write(dev, S626_LP_CRA(chan),
+ (cra ^ S626_CRAMSK_INDXPOL_A));
+ s626_debi_write(dev, S626_LP_CRA(chan), cra);
+ } else {
+ uint16_t crb;
+
+ crb = s626_debi_read(dev, S626_LP_CRB(chan));
+ crb &= ~S626_CRBMSK_INTCTRL;
+
+ /* Pulse index */
+ s626_debi_write(dev, S626_LP_CRB(chan),
+ (crb ^ S626_CRBMSK_INDXPOL_B));
+ s626_debi_write(dev, S626_LP_CRB(chan), crb);
+ }
+}
+
+static unsigned int s626_ai_reg_to_uint(unsigned int data)
+{
+ return ((data >> 18) & 0x3fff) ^ 0x2000;
+}
+
+static int s626_dio_set_irq(struct comedi_device *dev, unsigned int chan)
+{
+ unsigned int group = chan / 16;
+ unsigned int mask = 1 << (chan - (16 * group));
+ unsigned int status;
+
+ /* set channel to capture positive edge */
+ status = s626_debi_read(dev, S626_LP_RDEDGSEL(group));
+ s626_debi_write(dev, S626_LP_WREDGSEL(group), mask | status);
+
+ /* enable interrupt on selected channel */
+ status = s626_debi_read(dev, S626_LP_RDINTSEL(group));
+ s626_debi_write(dev, S626_LP_WRINTSEL(group), mask | status);
+
+ /* enable edge capture write command */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_EDCAP);
+
+ /* enable edge capture on selected channel */
+ status = s626_debi_read(dev, S626_LP_RDCAPSEL(group));
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), mask | status);
+
+ return 0;
+}
+
+static int s626_dio_reset_irq(struct comedi_device *dev, unsigned int group,
+ unsigned int mask)
+{
+ /* disable edge capture write command */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP);
+
+ /* enable edge capture on selected channel */
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), mask);
+
+ return 0;
+}
+
+static int s626_dio_clear_irq(struct comedi_device *dev)
+{
+ unsigned int group;
+
+ /* disable edge capture write command */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP);
+
+ /* clear all dio pending events and interrupt */
+ for (group = 0; group < S626_DIO_BANKS; group++)
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff);
+
+ return 0;
+}
+
+static void s626_handle_dio_interrupt(struct comedi_device *dev,
+ uint16_t irqbit, uint8_t group)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ s626_dio_reset_irq(dev, group, irqbit);
+
+ if (devpriv->ai_cmd_running) {
+ /* check if interrupt is an ai acquisition start trigger */
+ if ((irqbit >> (cmd->start_arg - (16 * group))) == 1 &&
+ cmd->start_src == TRIG_EXT) {
+ /* Start executing the RPS program */
+ s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ if (cmd->scan_begin_src == TRIG_EXT)
+ s626_dio_set_irq(dev, cmd->scan_begin_arg);
+ }
+ if ((irqbit >> (cmd->scan_begin_arg - (16 * group))) == 1 &&
+ cmd->scan_begin_src == TRIG_EXT) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+
+ if (cmd->convert_src == TRIG_EXT) {
+ devpriv->ai_convert_count = cmd->chanlist_len;
+
+ s626_dio_set_irq(dev, cmd->convert_arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ devpriv->ai_convert_count = cmd->chanlist_len;
+ s626_set_enable(dev, 5, S626_CLKENAB_ALWAYS);
+ }
+ }
+ if ((irqbit >> (cmd->convert_arg - (16 * group))) == 1 &&
+ cmd->convert_src == TRIG_EXT) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+
+ devpriv->ai_convert_count--;
+ if (devpriv->ai_convert_count > 0)
+ s626_dio_set_irq(dev, cmd->convert_arg);
+ }
+ }
+}
+
+static void s626_check_dio_interrupts(struct comedi_device *dev)
+{
+ uint16_t irqbit;
+ uint8_t group;
+
+ for (group = 0; group < S626_DIO_BANKS; group++) {
+ /* read interrupt type */
+ irqbit = s626_debi_read(dev, S626_LP_RDCAPFLG(group));
+
+ /* check if interrupt is generated from dio channels */
+ if (irqbit) {
+ s626_handle_dio_interrupt(dev, irqbit, group);
+ return;
+ }
+ }
+}
+
+static void s626_check_counter_interrupts(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ uint16_t irqbit;
+
+ /* read interrupt type */
+ irqbit = s626_debi_read(dev, S626_LP_RDMISC2);
+
+ /* check interrupt on counters */
+ if (irqbit & S626_IRQ_COINT1A) {
+ /* clear interrupt capture flag */
+ s626_reset_cap_flags(dev, 0);
+ }
+ if (irqbit & S626_IRQ_COINT2A) {
+ /* clear interrupt capture flag */
+ s626_reset_cap_flags(dev, 1);
+ }
+ if (irqbit & S626_IRQ_COINT3A) {
+ /* clear interrupt capture flag */
+ s626_reset_cap_flags(dev, 2);
+ }
+ if (irqbit & S626_IRQ_COINT1B) {
+ /* clear interrupt capture flag */
+ s626_reset_cap_flags(dev, 3);
+ }
+ if (irqbit & S626_IRQ_COINT2B) {
+ /* clear interrupt capture flag */
+ s626_reset_cap_flags(dev, 4);
+
+ if (devpriv->ai_convert_count > 0) {
+ devpriv->ai_convert_count--;
+ if (devpriv->ai_convert_count == 0)
+ s626_set_enable(dev, 4, S626_CLKENAB_INDEX);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS,
+ S626_P_MC2);
+ }
+ }
+ }
+ if (irqbit & S626_IRQ_COINT3B) {
+ /* clear interrupt capture flag */
+ s626_reset_cap_flags(dev, 5);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ devpriv->ai_convert_count = cmd->chanlist_len;
+ s626_set_enable(dev, 4, S626_CLKENAB_ALWAYS);
+ }
+ }
+}
+
+static bool s626_handle_eos_interrupt(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ /*
+ * Init ptr to DMA buffer that holds new ADC data. We skip the
+ * first uint16_t in the buffer because it contains junk data
+ * from the final ADC of the previous poll list scan.
+ */
+ uint32_t *readaddr = (uint32_t *)devpriv->ana_buf.logical_base + 1;
+ int i;
+
+ /* get the data and hand it over to comedi */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned short tempdata;
+
+ /*
+ * Convert ADC data to 16-bit integer values and copy
+ * to application buffer.
+ */
+ tempdata = s626_ai_reg_to_uint(*readaddr);
+ readaddr++;
+
+ comedi_buf_write_samples(s, &tempdata, 1);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+
+ if (async->events & COMEDI_CB_CANCEL_MASK)
+ devpriv->ai_cmd_running = 0;
+
+ if (devpriv->ai_cmd_running && cmd->scan_begin_src == TRIG_EXT)
+ s626_dio_set_irq(dev, cmd->scan_begin_arg);
+
+ comedi_handle_events(dev, s);
+
+ return !devpriv->ai_cmd_running;
+}
+
+static irqreturn_t s626_irq_handler(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ unsigned long flags;
+ uint32_t irqtype, irqstatus;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+ /* lock to avoid race with comedi_poll */
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ /* save interrupt enable register state */
+ irqstatus = readl(dev->mmio + S626_P_IER);
+
+ /* read interrupt type */
+ irqtype = readl(dev->mmio + S626_P_ISR);
+
+ /* disable master interrupt */
+ writel(0, dev->mmio + S626_P_IER);
+
+ /* clear interrupt */
+ writel(irqtype, dev->mmio + S626_P_ISR);
+
+ switch (irqtype) {
+ case S626_IRQ_RPS1: /* end_of_scan occurs */
+ if (s626_handle_eos_interrupt(dev))
+ irqstatus = 0;
+ break;
+ case S626_IRQ_GPIO3: /* check dio and counter interrupt */
+ /* s626_dio_clear_irq(dev); */
+ s626_check_dio_interrupts(dev);
+ s626_check_counter_interrupts(dev);
+ break;
+ }
+
+ /* enable interrupt */
+ writel(irqstatus, dev->mmio + S626_P_IER);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ return IRQ_HANDLED;
+}
+
+/*
+ * This function builds the RPS program for hardware driven acquisition.
+ */
+static void s626_reset_adc(struct comedi_device *dev, uint8_t *ppl)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ uint32_t *rps;
+ uint32_t jmp_adrs;
+ uint16_t i;
+ uint16_t n;
+ uint32_t local_ppl;
+
+ /* Stop RPS program in case it is currently running */
+ s626_mc_disable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ /* Set starting logical address to write RPS commands. */
+ rps = (uint32_t *)devpriv->rps_buf.logical_base;
+
+ /* Initialize RPS instruction pointer */
+ writel((uint32_t)devpriv->rps_buf.physical_base,
+ dev->mmio + S626_P_RPSADDR1);
+
+ /* Construct RPS program in rps_buf DMA buffer */
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ /* Wait for Start trigger. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC;
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_SIGADC;
+ }
+
+ /*
+ * SAA7146 BUG WORKAROUND Do a dummy DEBI Write. This is necessary
+ * because the first RPS DEBI Write following a non-RPS DEBI write
+ * seems to always fail. If we don't do this dummy write, the ADC
+ * gain might not be set to the value required for the first slot in
+ * the poll list; the ADC gain would instead remain unchanged from
+ * the previously programmed value.
+ */
+ /* Write DEBI Write command and address to shadow RAM. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2);
+ *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_GSEL;
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2);
+ /* Write DEBI immediate data to shadow RAM: */
+ *rps++ = S626_GSEL_BIPOLAR5V; /* arbitrary immediate data value. */
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI;
+ /* Reset "shadow RAM uploaded" flag. */
+ /* Invoke shadow RAM upload. */
+ *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI;
+ /* Wait for shadow upload to finish. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI;
+
+ /*
+ * Digitize all slots in the poll list. This is implemented as a
+ * for loop to limit the slot count to 16 in case the application
+ * forgot to set the S626_EOPL flag in the final slot.
+ */
+ for (devpriv->adc_items = 0; devpriv->adc_items < 16;
+ devpriv->adc_items++) {
+ /*
+ * Convert application's poll list item to private board class
+ * format. Each app poll list item is an uint8_t with form
+ * (EOPL,x,x,RANGE,CHAN<3:0>), where RANGE code indicates 0 =
+ * +-10V, 1 = +-5V, and EOPL = End of Poll List marker.
+ */
+ local_ppl = (*ppl << 8) | (*ppl & 0x10 ? S626_GSEL_BIPOLAR5V :
+ S626_GSEL_BIPOLAR10V);
+
+ /* Switch ADC analog gain. */
+ /* Write DEBI command and address to shadow RAM. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2);
+ *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_GSEL;
+ /* Write DEBI immediate data to shadow RAM. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2);
+ *rps++ = local_ppl;
+ /* Reset "shadow RAM uploaded" flag. */
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI;
+ /* Invoke shadow RAM upload. */
+ *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI;
+ /* Wait for shadow upload to finish. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI;
+ /* Select ADC analog input channel. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2);
+ /* Write DEBI command and address to shadow RAM. */
+ *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_ISEL;
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2);
+ /* Write DEBI immediate data to shadow RAM. */
+ *rps++ = local_ppl;
+ /* Reset "shadow RAM uploaded" flag. */
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI;
+ /* Invoke shadow RAM upload. */
+ *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI;
+ /* Wait for shadow upload to finish. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI;
+
+ /*
+ * Delay at least 10 microseconds for analog input settling.
+ * Instead of padding with NOPs, we use S626_RPS_JUMP
+ * instructions here; this allows us to produce a longer delay
+ * than is possible with NOPs because each S626_RPS_JUMP
+ * flushes the RPS' instruction prefetch pipeline.
+ */
+ jmp_adrs =
+ (uint32_t)devpriv->rps_buf.physical_base +
+ (uint32_t)((unsigned long)rps -
+ (unsigned long)devpriv->
+ rps_buf.logical_base);
+ for (i = 0; i < (10 * S626_RPSCLK_PER_US / 2); i++) {
+ jmp_adrs += 8; /* Repeat to implement time delay: */
+ /* Jump to next RPS instruction. */
+ *rps++ = S626_RPS_JUMP;
+ *rps++ = jmp_adrs;
+ }
+
+ if (cmd->convert_src != TRIG_NOW) {
+ /* Wait for Start trigger. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC;
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_SIGADC;
+ }
+ /* Start ADC by pulsing GPIO1. */
+ /* Begin ADC Start pulse. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
+ *rps++ = S626_GPIO_BASE | S626_GPIO1_LO;
+ *rps++ = S626_RPS_NOP;
+ /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */
+ /* End ADC Start pulse. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
+ *rps++ = S626_GPIO_BASE | S626_GPIO1_HI;
+ /*
+ * Wait for ADC to complete (GPIO2 is asserted high when ADC not
+ * busy) and for data from previous conversion to shift into FB
+ * BUFFER 1 register.
+ */
+ /* Wait for ADC done. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_GPIO2;
+
+ /* Transfer ADC data from FB BUFFER 1 register to DMA buffer. */
+ *rps++ = S626_RPS_STREG |
+ (S626_BUGFIX_STREG(S626_P_FB_BUFFER1) >> 2);
+ *rps++ = (uint32_t)devpriv->ana_buf.physical_base +
+ (devpriv->adc_items << 2);
+
+ /*
+ * If this slot's EndOfPollList flag is set, all channels have
+ * now been processed.
+ */
+ if (*ppl++ & S626_EOPL) {
+ devpriv->adc_items++; /* Adjust poll list item count. */
+ break; /* Exit poll list processing loop. */
+ }
+ }
+
+ /*
+ * VERSION 2.01 CHANGE: DELAY CHANGED FROM 250NS to 2US. Allow the
+ * ADC to stabilize for 2 microseconds before starting the final
+ * (dummy) conversion. This delay is necessary to allow sufficient
+ * time between last conversion finished and the start of the dummy
+ * conversion. Without this delay, the last conversion's data value
+ * is sometimes set to the previous conversion's data value.
+ */
+ for (n = 0; n < (2 * S626_RPSCLK_PER_US); n++)
+ *rps++ = S626_RPS_NOP;
+
+ /*
+ * Start a dummy conversion to cause the data from the last
+ * conversion of interest to be shifted in.
+ */
+ /* Begin ADC Start pulse. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
+ *rps++ = S626_GPIO_BASE | S626_GPIO1_LO;
+ *rps++ = S626_RPS_NOP;
+ /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2); /* End ADC Start pulse. */
+ *rps++ = S626_GPIO_BASE | S626_GPIO1_HI;
+
+ /*
+ * Wait for the data from the last conversion of interest to arrive
+ * in FB BUFFER 1 register.
+ */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_GPIO2; /* Wait for ADC done. */
+
+ /* Transfer final ADC data from FB BUFFER 1 register to DMA buffer. */
+ *rps++ = S626_RPS_STREG | (S626_BUGFIX_STREG(S626_P_FB_BUFFER1) >> 2);
+ *rps++ = (uint32_t)devpriv->ana_buf.physical_base +
+ (devpriv->adc_items << 2);
+
+ /* Indicate ADC scan loop is finished. */
+ /* Signal ReadADC() that scan is done. */
+ /* *rps++= S626_RPS_CLRSIGNAL | S626_RPS_SIGADC; */
+
+ /* invoke interrupt */
+ if (devpriv->ai_cmd_running == 1)
+ *rps++ = S626_RPS_IRQ;
+
+ /* Restart RPS program at its beginning. */
+ *rps++ = S626_RPS_JUMP; /* Branch to start of RPS program. */
+ *rps++ = (uint32_t)devpriv->rps_buf.physical_base;
+
+ /* End of RPS program build */
+}
+
+#ifdef unused_code
+static int s626_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct s626_private *devpriv = dev->private;
+ uint8_t i;
+ int32_t *readaddr;
+
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+
+ /* Wait until ADC scan loop is finished (RPS Signal 0 reset) */
+ while (s626_mc_test(dev, S626_MC2_ADC_RPS, S626_P_MC2))
+ ;
+
+ /*
+ * Init ptr to DMA buffer that holds new ADC data. We skip the
+ * first uint16_t in the buffer because it contains junk data from
+ * the final ADC of the previous poll list scan.
+ */
+ readaddr = (uint32_t *)devpriv->ana_buf.logical_base + 1;
+
+ /*
+ * Convert ADC data to 16-bit integer values and
+ * copy to application buffer.
+ */
+ for (i = 0; i < devpriv->adc_items; i++) {
+ *data = s626_ai_reg_to_uint(*readaddr++);
+ data++;
+ }
+
+ return i;
+}
+#endif
+
+static int s626_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = readl(dev->mmio + S626_P_PSR);
+ if (status & S626_PSR_GPIO2)
+ return 0;
+ return -EBUSY;
+}
+
+static int s626_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ uint16_t chan = CR_CHAN(insn->chanspec);
+ uint16_t range = CR_RANGE(insn->chanspec);
+ uint16_t adc_spec = 0;
+ uint32_t gpio_image;
+ uint32_t tmp;
+ int ret;
+ int n;
+
+ /*
+ * Convert application's ADC specification into form
+ * appropriate for register programming.
+ */
+ if (range == 0)
+ adc_spec = (chan << 8) | (S626_GSEL_BIPOLAR5V);
+ else
+ adc_spec = (chan << 8) | (S626_GSEL_BIPOLAR10V);
+
+ /* Switch ADC analog gain. */
+ s626_debi_write(dev, S626_LP_GSEL, adc_spec); /* Set gain. */
+
+ /* Select ADC analog input channel. */
+ s626_debi_write(dev, S626_LP_ISEL, adc_spec); /* Select channel. */
+
+ for (n = 0; n < insn->n; n++) {
+ /* Delay 10 microseconds for analog input settling. */
+ udelay(10);
+
+ /* Start ADC by pulsing GPIO1 low */
+ gpio_image = readl(dev->mmio + S626_P_GPIO);
+ /* Assert ADC Start command */
+ writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+ /* and stretch it out */
+ writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+ writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+ /* Negate ADC Start command */
+ writel(gpio_image | S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+
+ /*
+ * Wait for ADC to complete (GPIO2 is asserted high when
+ * ADC not busy) and for data from previous conversion to
+ * shift into FB BUFFER 1 register.
+ */
+
+ /* Wait for ADC done */
+ ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Fetch ADC data */
+ if (n != 0) {
+ tmp = readl(dev->mmio + S626_P_FB_BUFFER1);
+ data[n - 1] = s626_ai_reg_to_uint(tmp);
+ }
+
+ /*
+ * Allow the ADC to stabilize for 4 microseconds before
+ * starting the next (final) conversion. This delay is
+ * necessary to allow sufficient time between last
+ * conversion finished and the start of the next
+ * conversion. Without this delay, the last conversion's
+ * data value is sometimes set to the previous
+ * conversion's data value.
+ */
+ udelay(4);
+ }
+
+ /*
+ * Start a dummy conversion to cause the data from the
+ * previous conversion to be shifted in.
+ */
+ gpio_image = readl(dev->mmio + S626_P_GPIO);
+ /* Assert ADC Start command */
+ writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+ /* and stretch it out */
+ writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+ writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+ /* Negate ADC Start command */
+ writel(gpio_image | S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+
+ /* Wait for the data to arrive in FB BUFFER 1 register. */
+
+ /* Wait for ADC done */
+ ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Fetch ADC data from audio interface's input shift register. */
+
+ /* Fetch ADC data */
+ if (n != 0) {
+ tmp = readl(dev->mmio + S626_P_FB_BUFFER1);
+ data[n - 1] = s626_ai_reg_to_uint(tmp);
+ }
+
+ return n;
+}
+
+static int s626_ai_load_polllist(uint8_t *ppl, struct comedi_cmd *cmd)
+{
+ int n;
+
+ for (n = 0; n < cmd->chanlist_len; n++) {
+ if (CR_RANGE(cmd->chanlist[n]) == 0)
+ ppl[n] = CR_CHAN(cmd->chanlist[n]) | S626_RANGE_5V;
+ else
+ ppl[n] = CR_CHAN(cmd->chanlist[n]) | S626_RANGE_10V;
+ }
+ if (n != 0)
+ ppl[n - 1] |= S626_EOPL;
+
+ return n;
+}
+
+static int s626_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ /* Start executing the RPS program */
+ s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ s->async->inttrig = NULL;
+
+ return 1;
+}
+
+/*
+ * This function doesn't require a particular form, this is just what
+ * happens to be used in some of the drivers. It should convert ns
+ * nanoseconds to a counter value suitable for programming the device.
+ * Also, it should adjust ns so that it cooresponds to the actual time
+ * that the device will use.
+ */
+static int s626_ns_to_timer(unsigned int *nanosec, unsigned int flags)
+{
+ int divider, base;
+
+ base = 500; /* 2MHz internal clock */
+
+ switch (flags & CMDF_ROUND_MASK) {
+ case CMDF_ROUND_NEAREST:
+ default:
+ divider = DIV_ROUND_CLOSEST(*nanosec, base);
+ break;
+ case CMDF_ROUND_DOWN:
+ divider = (*nanosec) / base;
+ break;
+ case CMDF_ROUND_UP:
+ divider = DIV_ROUND_UP(*nanosec, base);
+ break;
+ }
+
+ *nanosec = base * divider;
+ return divider - 1;
+}
+
+static void s626_timer_load(struct comedi_device *dev,
+ unsigned int chan, int tick)
+{
+ uint16_t setup =
+ /* Preload upon index. */
+ S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) |
+ /* Disable hardware index. */
+ S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) |
+ /* Operating mode is Timer. */
+ S626_SET_STD_ENCMODE(S626_ENCMODE_TIMER) |
+ /* Count direction is Down. */
+ S626_SET_STD_CLKPOL(S626_CNTDIR_DOWN) |
+ /* Clock multiplier is 1x. */
+ S626_SET_STD_CLKMULT(S626_CLKMULT_1X) |
+ /* Enabled by index */
+ S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX);
+ uint16_t value_latchsrc = S626_LATCHSRC_A_INDXA;
+ /* uint16_t enab = S626_CLKENAB_ALWAYS; */
+
+ s626_set_mode(dev, chan, setup, false);
+
+ /* Set the preload register */
+ s626_preload(dev, chan, tick);
+
+ /*
+ * Software index pulse forces the preload register to load
+ * into the counter
+ */
+ s626_set_load_trig(dev, chan, 0);
+ s626_pulse_index(dev, chan);
+
+ /* set reload on counter overflow */
+ s626_set_load_trig(dev, chan, 1);
+
+ /* set interrupt on overflow */
+ s626_set_int_src(dev, chan, S626_INTSRC_OVER);
+
+ s626_set_latch_source(dev, chan, value_latchsrc);
+ /* s626_set_enable(dev, chan, (uint16_t)(enab != 0)); */
+}
+
+/* TO COMPLETE */
+static int s626_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct s626_private *devpriv = dev->private;
+ uint8_t ppl[16];
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int tick;
+
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_dev,
+ "s626_ai_cmd: Another ai_cmd is running\n");
+ return -EBUSY;
+ }
+ /* disable interrupt */
+ writel(0, dev->mmio + S626_P_IER);
+
+ /* clear interrupt request */
+ writel(S626_IRQ_RPS1 | S626_IRQ_GPIO3, dev->mmio + S626_P_ISR);
+
+ /* clear any pending interrupt */
+ s626_dio_clear_irq(dev);
+ /* s626_enc_clear_irq(dev); */
+
+ /* reset ai_cmd_running flag */
+ devpriv->ai_cmd_running = 0;
+
+ s626_ai_load_polllist(ppl, cmd);
+ devpriv->ai_cmd_running = 1;
+ devpriv->ai_convert_count = 0;
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_FOLLOW:
+ break;
+ case TRIG_TIMER:
+ /*
+ * set a counter to generate adc trigger at scan_begin_arg
+ * interval
+ */
+ tick = s626_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
+
+ /* load timer value and enable interrupt */
+ s626_timer_load(dev, 5, tick);
+ s626_set_enable(dev, 5, S626_CLKENAB_ALWAYS);
+ break;
+ case TRIG_EXT:
+ /* set the digital line and interrupt for scan trigger */
+ if (cmd->start_src != TRIG_EXT)
+ s626_dio_set_irq(dev, cmd->scan_begin_arg);
+ break;
+ }
+
+ switch (cmd->convert_src) {
+ case TRIG_NOW:
+ break;
+ case TRIG_TIMER:
+ /*
+ * set a counter to generate adc trigger at convert_arg
+ * interval
+ */
+ tick = s626_ns_to_timer(&cmd->convert_arg, cmd->flags);
+
+ /* load timer value and enable interrupt */
+ s626_timer_load(dev, 4, tick);
+ s626_set_enable(dev, 4, S626_CLKENAB_INDEX);
+ break;
+ case TRIG_EXT:
+ /* set the digital line and interrupt for convert trigger */
+ if (cmd->scan_begin_src != TRIG_EXT &&
+ cmd->start_src == TRIG_EXT)
+ s626_dio_set_irq(dev, cmd->convert_arg);
+ break;
+ }
+
+ s626_reset_adc(dev, ppl);
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ /* Trigger ADC scan loop start */
+ /* s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2); */
+
+ /* Start executing the RPS program */
+ s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
+ s->async->inttrig = NULL;
+ break;
+ case TRIG_EXT:
+ /* configure DIO channel for acquisition trigger */
+ s626_dio_set_irq(dev, cmd->start_arg);
+ s->async->inttrig = NULL;
+ break;
+ case TRIG_INT:
+ s->async->inttrig = s626_ai_inttrig;
+ break;
+ }
+
+ /* enable interrupt */
+ writel(S626_IRQ_GPIO3 | S626_IRQ_RPS1, dev->mmio + S626_P_IER);
+
+ return 0;
+}
+
+static int s626_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_INT | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT | TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_INT:
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ err |= comedi_check_trigger_arg_max(&cmd->start_arg, 39);
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_EXT)
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 39);
+ if (cmd->convert_src == TRIG_EXT)
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg, 39);
+
+#define S626_MAX_SPEED 200000 /* in nanoseconds */
+#define S626_MIN_SPEED 2000000000 /* in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ S626_MAX_SPEED);
+ err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
+ S626_MIN_SPEED);
+ } else {
+ /*
+ * external trigger
+ * should be level/edge, hi/lo specification here
+ * should specify multiple external triggers
+ * err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 9);
+ */
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+ S626_MAX_SPEED);
+ err |= comedi_check_trigger_arg_max(&cmd->convert_arg,
+ S626_MIN_SPEED);
+ } else {
+ /*
+ * external trigger - see above
+ * err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 9);
+ */
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ s626_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ s626_ns_to_timer(&arg, cmd->flags);
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int s626_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct s626_private *devpriv = dev->private;
+
+ /* Stop RPS program in case it is currently running */
+ s626_mc_disable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ /* disable master interrupt */
+ writel(0, dev->mmio + S626_P_IER);
+
+ devpriv->ai_cmd_running = 0;
+
+ return 0;
+}
+
+static int s626_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ int16_t dacdata = (int16_t)data[i];
+ int ret;
+
+ dacdata -= (0x1fff);
+
+ ret = s626_set_dac(dev, chan, dacdata);
+ if (ret)
+ return ret;
+
+ s->readback[chan] = data[i];
+ }
+
+ return insn->n;
+}
+
+/* *************** DIGITAL I/O FUNCTIONS *************** */
+
+/*
+ * All DIO functions address a group of DIO channels by means of
+ * "group" argument. group may be 0, 1 or 2, which correspond to DIO
+ * ports A, B and C, respectively.
+ */
+
+static void s626_dio_init(struct comedi_device *dev)
+{
+ uint16_t group;
+
+ /* Prepare to treat writes to WRCapSel as capture disables. */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP);
+
+ /* For each group of sixteen channels ... */
+ for (group = 0; group < S626_DIO_BANKS; group++) {
+ /* Disable all interrupts */
+ s626_debi_write(dev, S626_LP_WRINTSEL(group), 0);
+ /* Disable all event captures */
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff);
+ /* Init all DIOs to default edge polarity */
+ s626_debi_write(dev, S626_LP_WREDGSEL(group), 0);
+ /* Program all outputs to inactive state */
+ s626_debi_write(dev, S626_LP_WRDOUT(group), 0);
+ }
+}
+
+static int s626_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long group = (unsigned long)s->private;
+
+ if (comedi_dio_update_state(s, data))
+ s626_debi_write(dev, S626_LP_WRDOUT(group), s->state);
+
+ data[1] = s626_debi_read(dev, S626_LP_RDDIN(group));
+
+ return insn->n;
+}
+
+static int s626_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long group = (unsigned long)s->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ s626_debi_write(dev, S626_LP_WRDOUT(group), s->io_bits);
+
+ return insn->n;
+}
+
+/*
+ * Now this function initializes the value of the counter (data[0])
+ * and set the subdevice. To complete with trigger and interrupt
+ * configuration.
+ *
+ * FIXME: data[0] is supposed to be an INSN_CONFIG_xxx constant indicating
+ * what is being configured, but this function appears to be using data[0]
+ * as a variable.
+ */
+static int s626_enc_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint16_t setup =
+ /* Preload upon index. */
+ S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) |
+ /* Disable hardware index. */
+ S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) |
+ /* Operating mode is Counter. */
+ S626_SET_STD_ENCMODE(S626_ENCMODE_COUNTER) |
+ /* Active high clock. */
+ S626_SET_STD_CLKPOL(S626_CLKPOL_POS) |
+ /* Clock multiplier is 1x. */
+ S626_SET_STD_CLKMULT(S626_CLKMULT_1X) |
+ /* Enabled by index */
+ S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX);
+ /* uint16_t disable_int_src = true; */
+ /* uint32_t Preloadvalue; //Counter initial value */
+ uint16_t value_latchsrc = S626_LATCHSRC_AB_READ;
+ uint16_t enab = S626_CLKENAB_ALWAYS;
+
+ /* (data==NULL) ? (Preloadvalue=0) : (Preloadvalue=data[0]); */
+
+ s626_set_mode(dev, chan, setup, true);
+ s626_preload(dev, chan, data[0]);
+ s626_pulse_index(dev, chan);
+ s626_set_latch_source(dev, chan, value_latchsrc);
+ s626_set_enable(dev, chan, (enab != 0));
+
+ return insn->n;
+}
+
+static int s626_enc_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint16_t cntr_latch_reg = S626_LP_CNTR(chan);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val;
+
+ /*
+ * Read the counter's output latch LSW/MSW.
+ * Latches on LSW read.
+ */
+ val = s626_debi_read(dev, cntr_latch_reg);
+ val |= (s626_debi_read(dev, cntr_latch_reg + 2) << 16);
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int s626_enc_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /* Set the preload register */
+ s626_preload(dev, chan, data[0]);
+
+ /*
+ * Software index pulse forces the preload register to load
+ * into the counter
+ */
+ s626_set_load_trig(dev, chan, 0);
+ s626_pulse_index(dev, chan);
+ s626_set_load_trig(dev, chan, 2);
+
+ return 1;
+}
+
+static void s626_write_misc2(struct comedi_device *dev, uint16_t new_image)
+{
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_WENABLE);
+ s626_debi_write(dev, S626_LP_WRMISC2, new_image);
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_WDISABLE);
+}
+
+static void s626_counters_init(struct comedi_device *dev)
+{
+ int chan;
+ uint16_t setup =
+ /* Preload upon index. */
+ S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) |
+ /* Disable hardware index. */
+ S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) |
+ /* Operating mode is counter. */
+ S626_SET_STD_ENCMODE(S626_ENCMODE_COUNTER) |
+ /* Active high clock. */
+ S626_SET_STD_CLKPOL(S626_CLKPOL_POS) |
+ /* Clock multiplier is 1x. */
+ S626_SET_STD_CLKMULT(S626_CLKMULT_1X) |
+ /* Enabled by index */
+ S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX);
+
+ /*
+ * Disable all counter interrupts and clear any captured counter events.
+ */
+ for (chan = 0; chan < S626_ENCODER_CHANNELS; chan++) {
+ s626_set_mode(dev, chan, setup, true);
+ s626_set_int_src(dev, chan, 0);
+ s626_reset_cap_flags(dev, chan);
+ s626_set_enable(dev, chan, S626_CLKENAB_ALWAYS);
+ }
+}
+
+static int s626_allocate_dma_buffers(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct s626_private *devpriv = dev->private;
+ void *addr;
+ dma_addr_t appdma;
+
+ addr = pci_alloc_consistent(pcidev, S626_DMABUF_SIZE, &appdma);
+ if (!addr)
+ return -ENOMEM;
+ devpriv->ana_buf.logical_base = addr;
+ devpriv->ana_buf.physical_base = appdma;
+
+ addr = pci_alloc_consistent(pcidev, S626_DMABUF_SIZE, &appdma);
+ if (!addr)
+ return -ENOMEM;
+ devpriv->rps_buf.logical_base = addr;
+ devpriv->rps_buf.physical_base = appdma;
+
+ return 0;
+}
+
+static void s626_free_dma_buffers(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct s626_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ if (devpriv->rps_buf.logical_base)
+ pci_free_consistent(pcidev, S626_DMABUF_SIZE,
+ devpriv->rps_buf.logical_base,
+ devpriv->rps_buf.physical_base);
+ if (devpriv->ana_buf.logical_base)
+ pci_free_consistent(pcidev, S626_DMABUF_SIZE,
+ devpriv->ana_buf.logical_base,
+ devpriv->ana_buf.physical_base);
+}
+
+static int s626_initialize(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+ dma_addr_t phys_buf;
+ uint16_t chan;
+ int i;
+ int ret;
+
+ /* Enable DEBI and audio pins, enable I2C interface */
+ s626_mc_enable(dev, S626_MC1_DEBI | S626_MC1_AUDIO | S626_MC1_I2C,
+ S626_P_MC1);
+
+ /*
+ * Configure DEBI operating mode
+ *
+ * Local bus is 16 bits wide
+ * Declare DEBI transfer timeout interval
+ * Set up byte lane steering
+ * Intel-compatible local bus (DEBI never times out)
+ */
+ writel(S626_DEBI_CFG_SLAVE16 |
+ (S626_DEBI_TOUT << S626_DEBI_CFG_TOUT_BIT) | S626_DEBI_SWAP |
+ S626_DEBI_CFG_INTEL, dev->mmio + S626_P_DEBICFG);
+
+ /* Disable MMU paging */
+ writel(S626_DEBI_PAGE_DISABLE, dev->mmio + S626_P_DEBIPAGE);
+
+ /* Init GPIO so that ADC Start* is negated */
+ writel(S626_GPIO_BASE | S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
+
+ /* I2C device address for onboard eeprom (revb) */
+ devpriv->i2c_adrs = 0xA0;
+
+ /*
+ * Issue an I2C ABORT command to halt any I2C
+ * operation in progress and reset BUSY flag.
+ */
+ writel(S626_I2C_CLKSEL | S626_I2C_ABORT,
+ dev->mmio + S626_P_I2CSTAT);
+ s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Per SAA7146 data sheet, write to STATUS
+ * reg twice to reset all I2C error flags.
+ */
+ for (i = 0; i < 2; i++) {
+ writel(S626_I2C_CLKSEL, dev->mmio + S626_P_I2CSTAT);
+ s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Init audio interface functional attributes: set DAC/ADC
+ * serial clock rates, invert DAC serial clock so that
+ * DAC data setup times are satisfied, enable DAC serial
+ * clock out.
+ */
+ writel(S626_ACON2_INIT, dev->mmio + S626_P_ACON2);
+
+ /*
+ * Set up TSL1 slot list, which is used to control the
+ * accumulation of ADC data: S626_RSD1 = shift data in on SD1.
+ * S626_SIB_A1 = store data uint8_t at next available location
+ * in FB BUFFER1 register.
+ */
+ writel(S626_RSD1 | S626_SIB_A1, dev->mmio + S626_P_TSL1);
+ writel(S626_RSD1 | S626_SIB_A1 | S626_EOS,
+ dev->mmio + S626_P_TSL1 + 4);
+
+ /* Enable TSL1 slot list so that it executes all the time */
+ writel(S626_ACON1_ADCSTART, dev->mmio + S626_P_ACON1);
+
+ /*
+ * Initialize RPS registers used for ADC
+ */
+
+ /* Physical start of RPS program */
+ writel((uint32_t)devpriv->rps_buf.physical_base,
+ dev->mmio + S626_P_RPSADDR1);
+ /* RPS program performs no explicit mem writes */
+ writel(0, dev->mmio + S626_P_RPSPAGE1);
+ /* Disable RPS timeouts */
+ writel(0, dev->mmio + S626_P_RPS1_TOUT);
+
+#if 0
+ /*
+ * SAA7146 BUG WORKAROUND
+ *
+ * Initialize SAA7146 ADC interface to a known state by
+ * invoking ADCs until FB BUFFER 1 register shows that it
+ * is correctly receiving ADC data. This is necessary
+ * because the SAA7146 ADC interface does not start up in
+ * a defined state after a PCI reset.
+ */
+ {
+ struct comedi_subdevice *s = dev->read_subdev;
+ uint8_t poll_list;
+ uint16_t adc_data;
+ uint16_t start_val;
+ uint16_t index;
+ unsigned int data[16];
+
+ /* Create a simple polling list for analog input channel 0 */
+ poll_list = S626_EOPL;
+ s626_reset_adc(dev, &poll_list);
+
+ /* Get initial ADC value */
+ s626_ai_rinsn(dev, s, NULL, data);
+ start_val = data[0];
+
+ /*
+ * VERSION 2.01 CHANGE: TIMEOUT ADDED TO PREVENT HANGED
+ * EXECUTION.
+ *
+ * Invoke ADCs until the new ADC value differs from the initial
+ * value or a timeout occurs. The timeout protects against the
+ * possibility that the driver is restarting and the ADC data is
+ * a fixed value resulting from the applied ADC analog input
+ * being unusually quiet or at the rail.
+ */
+ for (index = 0; index < 500; index++) {
+ s626_ai_rinsn(dev, s, NULL, data);
+ adc_data = data[0];
+ if (adc_data != start_val)
+ break;
+ }
+ }
+#endif /* SAA7146 BUG WORKAROUND */
+
+ /*
+ * Initialize the DAC interface
+ */
+
+ /*
+ * Init Audio2's output DMAC attributes:
+ * burst length = 1 DWORD
+ * threshold = 1 DWORD.
+ */
+ writel(0, dev->mmio + S626_P_PCI_BT_A);
+
+ /*
+ * Init Audio2's output DMA physical addresses. The protection
+ * address is set to 1 DWORD past the base address so that a
+ * single DWORD will be transferred each time a DMA transfer is
+ * enabled.
+ */
+ phys_buf = devpriv->ana_buf.physical_base +
+ (S626_DAC_WDMABUF_OS * sizeof(uint32_t));
+ writel((uint32_t)phys_buf, dev->mmio + S626_P_BASEA2_OUT);
+ writel((uint32_t)(phys_buf + sizeof(uint32_t)),
+ dev->mmio + S626_P_PROTA2_OUT);
+
+ /*
+ * Cache Audio2's output DMA buffer logical address. This is
+ * where DAC data is buffered for A2 output DMA transfers.
+ */
+ devpriv->dac_wbuf = (uint32_t *)devpriv->ana_buf.logical_base +
+ S626_DAC_WDMABUF_OS;
+
+ /*
+ * Audio2's output channels does not use paging. The
+ * protection violation handling bit is set so that the
+ * DMAC will automatically halt and its PCI address pointer
+ * will be reset when the protection address is reached.
+ */
+ writel(8, dev->mmio + S626_P_PAGEA2_OUT);
+
+ /*
+ * Initialize time slot list 2 (TSL2), which is used to control
+ * the clock generation for and serialization of data to be sent
+ * to the DAC devices. Slot 0 is a NOP that is used to trap TSL
+ * execution; this permits other slots to be safely modified
+ * without first turning off the TSL sequencer (which is
+ * apparently impossible to do). Also, SD3 (which is driven by a
+ * pull-up resistor) is shifted in and stored to the MSB of
+ * FB_BUFFER2 to be used as evidence that the slot sequence has
+ * not yet finished executing.
+ */
+
+ /* Slot 0: Trap TSL execution, shift 0xFF into FB_BUFFER2 */
+ writel(S626_XSD2 | S626_RSD3 | S626_SIB_A2 | S626_EOS,
+ dev->mmio + S626_VECTPORT(0));
+
+ /*
+ * Initialize slot 1, which is constant. Slot 1 causes a
+ * DWORD to be transferred from audio channel 2's output FIFO
+ * to the FIFO's output buffer so that it can be serialized
+ * and sent to the DAC during subsequent slots. All remaining
+ * slots are dynamically populated as required by the target
+ * DAC device.
+ */
+
+ /* Slot 1: Fetch DWORD from Audio2's output FIFO */
+ writel(S626_LF_A2, dev->mmio + S626_VECTPORT(1));
+
+ /* Start DAC's audio interface (TSL2) running */
+ writel(S626_ACON1_DACSTART, dev->mmio + S626_P_ACON1);
+
+ /*
+ * Init Trim DACs to calibrated values. Do it twice because the
+ * SAA7146 audio channel does not always reset properly and
+ * sometimes causes the first few TrimDAC writes to malfunction.
+ */
+ s626_load_trim_dacs(dev);
+ ret = s626_load_trim_dacs(dev);
+ if (ret)
+ return ret;
+
+ /*
+ * Manually init all gate array hardware in case this is a soft
+ * reset (we have no way of determining whether this is a warm
+ * or cold start). This is necessary because the gate array will
+ * reset only in response to a PCI hard reset; there is no soft
+ * reset function.
+ */
+
+ /*
+ * Init all DAC outputs to 0V and init all DAC setpoint and
+ * polarity images.
+ */
+ for (chan = 0; chan < S626_DAC_CHANNELS; chan++) {
+ ret = s626_set_dac(dev, chan, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Init counters */
+ s626_counters_init(dev);
+
+ /*
+ * Without modifying the state of the Battery Backup enab, disable
+ * the watchdog timer, set DIO channels 0-5 to operate in the
+ * standard DIO (vs. counter overflow) mode, disable the battery
+ * charger, and reset the watchdog interval selector to zero.
+ */
+ s626_write_misc2(dev, (s626_debi_read(dev, S626_LP_RDMISC2) &
+ S626_MISC2_BATT_ENABLE));
+
+ /* Initialize the digital I/O subsystem */
+ s626_dio_init(dev);
+
+ return 0;
+}
+
+static int s626_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct s626_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->mmio = pci_ioremap_bar(pcidev, 0);
+ if (!dev->mmio)
+ return -ENOMEM;
+
+ /* disable master interrupt */
+ writel(0, dev->mmio + S626_P_IER);
+
+ /* soft reset */
+ writel(S626_MC1_SOFT_RESET, dev->mmio + S626_P_MC1);
+
+ /* DMA FIXME DMA// */
+
+ ret = s626_allocate_dma_buffers(dev);
+ if (ret)
+ return ret;
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, s626_irq_handler, IRQF_SHARED,
+ dev->board_name, dev);
+
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 6);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = S626_ADC_CHANNELS;
+ s->maxdata = 0x3fff;
+ s->range_table = &s626_range_table;
+ s->len_chanlist = S626_ADC_CHANNELS;
+ s->insn_read = s626_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmd = s626_ai_cmd;
+ s->do_cmdtest = s626_ai_cmdtest;
+ s->cancel = s626_ai_cancel;
+ }
+
+ s = &dev->subdevices[1];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = S626_DAC_CHANNELS;
+ s->maxdata = 0x3fff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = s626_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[2];
+ /* digital I/O subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->io_bits = 0xffff;
+ s->private = (void *)0; /* DIO group 0 */
+ s->range_table = &range_digital;
+ s->insn_config = s626_dio_insn_config;
+ s->insn_bits = s626_dio_insn_bits;
+
+ s = &dev->subdevices[3];
+ /* digital I/O subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->io_bits = 0xffff;
+ s->private = (void *)1; /* DIO group 1 */
+ s->range_table = &range_digital;
+ s->insn_config = s626_dio_insn_config;
+ s->insn_bits = s626_dio_insn_bits;
+
+ s = &dev->subdevices[4];
+ /* digital I/O subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->io_bits = 0xffff;
+ s->private = (void *)2; /* DIO group 2 */
+ s->range_table = &range_digital;
+ s->insn_config = s626_dio_insn_config;
+ s->insn_bits = s626_dio_insn_bits;
+
+ s = &dev->subdevices[5];
+ /* encoder (counter) subdevice */
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = S626_ENCODER_CHANNELS;
+ s->maxdata = 0xffffff;
+ s->range_table = &range_unknown;
+ s->insn_config = s626_enc_insn_config;
+ s->insn_read = s626_enc_insn_read;
+ s->insn_write = s626_enc_insn_write;
+
+ ret = s626_initialize(dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void s626_detach(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+
+ if (devpriv) {
+ /* stop ai_command */
+ devpriv->ai_cmd_running = 0;
+
+ if (dev->mmio) {
+ /* interrupt mask */
+ /* Disable master interrupt */
+ writel(0, dev->mmio + S626_P_IER);
+ /* Clear board's IRQ status flag */
+ writel(S626_IRQ_GPIO3 | S626_IRQ_RPS1,
+ dev->mmio + S626_P_ISR);
+
+ /* Disable the watchdog timer and battery charger. */
+ s626_write_misc2(dev, 0);
+
+ /* Close all interfaces on 7146 device */
+ writel(S626_MC1_SHUTDOWN, dev->mmio + S626_P_MC1);
+ writel(S626_ACON1_BASE, dev->mmio + S626_P_ACON1);
+ }
+ }
+ comedi_pci_detach(dev);
+ s626_free_dma_buffers(dev);
+}
+
+static struct comedi_driver s626_driver = {
+ .driver_name = "s626",
+ .module = THIS_MODULE,
+ .auto_attach = s626_auto_attach,
+ .detach = s626_detach,
+};
+
+static int s626_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &s626_driver, id->driver_data);
+}
+
+/*
+ * For devices with vendor:device id == 0x1131:0x7146 you must specify
+ * also subvendor:subdevice ids, because otherwise it will conflict with
+ * Philips SAA7146 media/dvb based cards.
+ */
+static const struct pci_device_id s626_pci_table[] = {
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146,
+ 0x6000, 0x0272) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, s626_pci_table);
+
+static struct pci_driver s626_pci_driver = {
+ .name = "s626",
+ .id_table = s626_pci_table,
+ .probe = s626_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(s626_driver, s626_pci_driver);
+
+MODULE_AUTHOR("Gianluca Palli <gpalli@deis.unibo.it>");
+MODULE_DESCRIPTION("Sensoray 626 Comedi driver module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/s626.h b/drivers/staging/comedi/drivers/s626.h
new file mode 100644
index 000000000..b83424e75
--- /dev/null
+++ b/drivers/staging/comedi/drivers/s626.h
@@ -0,0 +1,760 @@
+/*
+ * comedi/drivers/s626.h
+ * Sensoray s626 Comedi driver, header file
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * Based on Sensoray Model 626 Linux driver Version 0.2
+ * Copyright (C) 2002-2004 Sensoray Co., Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef S626_H_INCLUDED
+#define S626_H_INCLUDED
+
+#define S626_DMABUF_SIZE 4096 /* 4k pages */
+
+#define S626_ADC_CHANNELS 16
+#define S626_DAC_CHANNELS 4
+#define S626_ENCODER_CHANNELS 6
+#define S626_DIO_CHANNELS 48
+#define S626_DIO_BANKS 3 /* Number of DIO groups. */
+#define S626_DIO_EXTCHANS 40 /* Number of extended-capability
+ * DIO channels. */
+
+#define S626_NUM_TRIMDACS 12 /* Number of valid TrimDAC channels. */
+
+/* PCI bus interface types. */
+#define S626_INTEL 1 /* Intel bus type. */
+#define S626_MOTOROLA 2 /* Motorola bus type. */
+
+#define S626_PLATFORM S626_INTEL /* *** SELECT PLATFORM TYPE *** */
+
+#define S626_RANGE_5V 0x10 /* +/-5V range */
+#define S626_RANGE_10V 0x00 /* +/-10V range */
+
+#define S626_EOPL 0x80 /* End of ADC poll list marker. */
+#define S626_GSEL_BIPOLAR5V 0x00F0 /* S626_LP_GSEL setting 5V bipolar. */
+#define S626_GSEL_BIPOLAR10V 0x00A0 /* S626_LP_GSEL setting 10V bipolar. */
+
+/* Error codes that must be visible to this base class. */
+#define S626_ERR_ILLEGAL_PARM 0x00010000 /* Illegal function parameter
+ * value was specified. */
+#define S626_ERR_I2C 0x00020000 /* I2C error. */
+#define S626_ERR_COUNTERSETUP 0x00200000 /* Illegal setup specified for
+ * counter channel. */
+#define S626_ERR_DEBI_TIMEOUT 0x00400000 /* DEBI transfer timed out. */
+
+/*
+ * Organization (physical order) and size (in DWORDs) of logical DMA buffers
+ * contained by ANA_DMABUF.
+ */
+#define S626_ADC_DMABUF_DWORDS 40 /* ADC DMA buffer must hold 16 samples,
+ * plus pre/post garbage samples. */
+#define S626_DAC_WDMABUF_DWORDS 1 /* DAC output DMA buffer holds a single
+ * sample. */
+
+/* All remaining space in 4KB DMA buffer is available for the RPS1 program. */
+
+/* Address offsets, in DWORDS, from base of DMA buffer. */
+#define S626_DAC_WDMABUF_OS S626_ADC_DMABUF_DWORDS
+
+/* Interrupt enable bit in ISR and IER. */
+#define S626_IRQ_GPIO3 0x00000040 /* IRQ enable for GPIO3. */
+#define S626_IRQ_RPS1 0x10000000
+#define S626_ISR_AFOU 0x00000800
+/* Audio fifo under/overflow detected. */
+
+#define S626_IRQ_COINT1A 0x0400 /* counter 1A overflow interrupt mask */
+#define S626_IRQ_COINT1B 0x0800 /* counter 1B overflow interrupt mask */
+#define S626_IRQ_COINT2A 0x1000 /* counter 2A overflow interrupt mask */
+#define S626_IRQ_COINT2B 0x2000 /* counter 2B overflow interrupt mask */
+#define S626_IRQ_COINT3A 0x4000 /* counter 3A overflow interrupt mask */
+#define S626_IRQ_COINT3B 0x8000 /* counter 3B overflow interrupt mask */
+
+/* RPS command codes. */
+#define S626_RPS_CLRSIGNAL 0x00000000 /* CLEAR SIGNAL */
+#define S626_RPS_SETSIGNAL 0x10000000 /* SET SIGNAL */
+#define S626_RPS_NOP 0x00000000 /* NOP */
+#define S626_RPS_PAUSE 0x20000000 /* PAUSE */
+#define S626_RPS_UPLOAD 0x40000000 /* UPLOAD */
+#define S626_RPS_JUMP 0x80000000 /* JUMP */
+#define S626_RPS_LDREG 0x90000100 /* LDREG (1 uint32_t only) */
+#define S626_RPS_STREG 0xA0000100 /* STREG (1 uint32_t only) */
+#define S626_RPS_STOP 0x50000000 /* STOP */
+#define S626_RPS_IRQ 0x60000000 /* IRQ */
+
+#define S626_RPS_LOGICAL_OR 0x08000000 /* Logical OR conditionals. */
+#define S626_RPS_INVERT 0x04000000 /* Test for negated
+ * semaphores. */
+#define S626_RPS_DEBI 0x00000002 /* DEBI done */
+
+#define S626_RPS_SIG0 0x00200000 /* RPS semaphore 0
+ * (used by ADC). */
+#define S626_RPS_SIG1 0x00400000 /* RPS semaphore 1
+ * (used by DAC). */
+#define S626_RPS_SIG2 0x00800000 /* RPS semaphore 2
+ * (not used). */
+#define S626_RPS_GPIO2 0x00080000 /* RPS GPIO2 */
+#define S626_RPS_GPIO3 0x00100000 /* RPS GPIO3 */
+
+#define S626_RPS_SIGADC S626_RPS_SIG0 /* Trigger/status for
+ * ADC's RPS program. */
+#define S626_RPS_SIGDAC S626_RPS_SIG1 /* Trigger/status for
+ * DAC's RPS program. */
+
+/* RPS clock parameters. */
+#define S626_RPSCLK_SCALAR 8 /* This is apparent ratio of
+ * PCI/RPS clks (undocumented!!). */
+#define S626_RPSCLK_PER_US (33 / S626_RPSCLK_SCALAR)
+ /* Number of RPS clocks in one
+ * microsecond. */
+
+/* Event counter source addresses. */
+#define S626_SBA_RPS_A0 0x27 /* Time of RPS0 busy, in PCI clocks. */
+
+/* GPIO constants. */
+#define S626_GPIO_BASE 0x10004000 /* GPIO 0,2,3 = inputs,
+ * GPIO3 = IRQ; GPIO1 = out. */
+#define S626_GPIO1_LO 0x00000000 /* GPIO1 set to LOW. */
+#define S626_GPIO1_HI 0x00001000 /* GPIO1 set to HIGH. */
+
+/* Primary Status Register (PSR) constants. */
+#define S626_PSR_DEBI_E 0x00040000 /* DEBI event flag. */
+#define S626_PSR_DEBI_S 0x00080000 /* DEBI status flag. */
+#define S626_PSR_A2_IN 0x00008000 /* Audio output DMA2 protection
+ * address reached. */
+#define S626_PSR_AFOU 0x00000800 /* Audio FIFO under/overflow
+ * detected. */
+#define S626_PSR_GPIO2 0x00000020 /* GPIO2 input pin: 0=AdcBusy,
+ * 1=AdcIdle. */
+#define S626_PSR_EC0S 0x00000001 /* Event counter 0 threshold
+ * reached. */
+
+/* Secondary Status Register (SSR) constants. */
+#define S626_SSR_AF2_OUT 0x00000200 /* Audio 2 output FIFO
+ * under/overflow detected. */
+
+/* Master Control Register 1 (MC1) constants. */
+#define S626_MC1_SOFT_RESET 0x80000000 /* Invoke 7146 soft reset. */
+#define S626_MC1_SHUTDOWN 0x3FFF0000 /* Shut down all MC1-controlled
+ * enables. */
+
+#define S626_MC1_ERPS1 0x2000 /* Enab/disable RPS task 1. */
+#define S626_MC1_ERPS0 0x1000 /* Enab/disable RPS task 0. */
+#define S626_MC1_DEBI 0x0800 /* Enab/disable DEBI pins. */
+#define S626_MC1_AUDIO 0x0200 /* Enab/disable audio port pins. */
+#define S626_MC1_I2C 0x0100 /* Enab/disable I2C interface. */
+#define S626_MC1_A2OUT 0x0008 /* Enab/disable transfer on A2 out. */
+#define S626_MC1_A2IN 0x0004 /* Enab/disable transfer on A2 in. */
+#define S626_MC1_A1IN 0x0001 /* Enab/disable transfer on A1 in. */
+
+/* Master Control Register 2 (MC2) constants. */
+#define S626_MC2_UPLD_DEBI 0x0002 /* Upload DEBI. */
+#define S626_MC2_UPLD_IIC 0x0001 /* Upload I2C. */
+#define S626_MC2_RPSSIG2 0x2000 /* RPS signal 2 (not used). */
+#define S626_MC2_RPSSIG1 0x1000 /* RPS signal 1 (DAC RPS busy). */
+#define S626_MC2_RPSSIG0 0x0800 /* RPS signal 0 (ADC RPS busy). */
+
+#define S626_MC2_ADC_RPS S626_MC2_RPSSIG0 /* ADC RPS busy. */
+#define S626_MC2_DAC_RPS S626_MC2_RPSSIG1 /* DAC RPS busy. */
+
+/* PCI BUS (SAA7146) REGISTER ADDRESS OFFSETS */
+#define S626_P_PCI_BT_A 0x004C /* Audio DMA burst/threshold control. */
+#define S626_P_DEBICFG 0x007C /* DEBI configuration. */
+#define S626_P_DEBICMD 0x0080 /* DEBI command. */
+#define S626_P_DEBIPAGE 0x0084 /* DEBI page. */
+#define S626_P_DEBIAD 0x0088 /* DEBI target address. */
+#define S626_P_I2CCTRL 0x008C /* I2C control. */
+#define S626_P_I2CSTAT 0x0090 /* I2C status. */
+#define S626_P_BASEA2_IN 0x00AC /* Audio input 2 base physical DMAbuf
+ * address. */
+#define S626_P_PROTA2_IN 0x00B0 /* Audio input 2 physical DMAbuf
+ * protection address. */
+#define S626_P_PAGEA2_IN 0x00B4 /* Audio input 2 paging attributes. */
+#define S626_P_BASEA2_OUT 0x00B8 /* Audio output 2 base physical DMAbuf
+ * address. */
+#define S626_P_PROTA2_OUT 0x00BC /* Audio output 2 physical DMAbuf
+ * protection address. */
+#define S626_P_PAGEA2_OUT 0x00C0 /* Audio output 2 paging attributes. */
+#define S626_P_RPSPAGE0 0x00C4 /* RPS0 page. */
+#define S626_P_RPSPAGE1 0x00C8 /* RPS1 page. */
+#define S626_P_RPS0_TOUT 0x00D4 /* RPS0 time-out. */
+#define S626_P_RPS1_TOUT 0x00D8 /* RPS1 time-out. */
+#define S626_P_IER 0x00DC /* Interrupt enable. */
+#define S626_P_GPIO 0x00E0 /* General-purpose I/O. */
+#define S626_P_EC1SSR 0x00E4 /* Event counter set 1 source select. */
+#define S626_P_ECT1R 0x00EC /* Event counter threshold set 1. */
+#define S626_P_ACON1 0x00F4 /* Audio control 1. */
+#define S626_P_ACON2 0x00F8 /* Audio control 2. */
+#define S626_P_MC1 0x00FC /* Master control 1. */
+#define S626_P_MC2 0x0100 /* Master control 2. */
+#define S626_P_RPSADDR0 0x0104 /* RPS0 instruction pointer. */
+#define S626_P_RPSADDR1 0x0108 /* RPS1 instruction pointer. */
+#define S626_P_ISR 0x010C /* Interrupt status. */
+#define S626_P_PSR 0x0110 /* Primary status. */
+#define S626_P_SSR 0x0114 /* Secondary status. */
+#define S626_P_EC1R 0x0118 /* Event counter set 1. */
+#define S626_P_ADP4 0x0138 /* Logical audio DMA pointer of audio
+ * input FIFO A2_IN. */
+#define S626_P_FB_BUFFER1 0x0144 /* Audio feedback buffer 1. */
+#define S626_P_FB_BUFFER2 0x0148 /* Audio feedback buffer 2. */
+#define S626_P_TSL1 0x0180 /* Audio time slot list 1. */
+#define S626_P_TSL2 0x01C0 /* Audio time slot list 2. */
+
+/* LOCAL BUS (GATE ARRAY) REGISTER ADDRESS OFFSETS */
+/* Analog I/O registers: */
+#define S626_LP_DACPOL 0x0082 /* Write DAC polarity. */
+#define S626_LP_GSEL 0x0084 /* Write ADC gain. */
+#define S626_LP_ISEL 0x0086 /* Write ADC channel select. */
+
+/* Digital I/O registers */
+#define S626_LP_RDDIN(x) (0x0040 + (x) * 0x10) /* R: digital input */
+#define S626_LP_WRINTSEL(x) (0x0042 + (x) * 0x10) /* W: int enable */
+#define S626_LP_WREDGSEL(x) (0x0044 + (x) * 0x10) /* W: edge selection */
+#define S626_LP_WRCAPSEL(x) (0x0046 + (x) * 0x10) /* W: capture enable */
+#define S626_LP_RDCAPFLG(x) (0x0048 + (x) * 0x10) /* R: edges captured */
+#define S626_LP_WRDOUT(x) (0x0048 + (x) * 0x10) /* W: digital output */
+#define S626_LP_RDINTSEL(x) (0x004a + (x) * 0x10) /* R: int enable */
+#define S626_LP_RDEDGSEL(x) (0x004c + (x) * 0x10) /* R: edge selection */
+#define S626_LP_RDCAPSEL(x) (0x004e + (x) * 0x10) /* R: capture enable */
+
+/* Counter registers (read/write): 0A 1A 2A 0B 1B 2B */
+#define S626_LP_CRA(x) (0x0000 + (((x) % 3) * 0x4))
+#define S626_LP_CRB(x) (0x0002 + (((x) % 3) * 0x4))
+
+/* Counter PreLoad (write) and Latch (read) Registers: 0A 1A 2A 0B 1B 2B */
+#define S626_LP_CNTR(x) (0x000c + (((x) < 3) ? 0x0 : 0x4) + \
+ (((x) % 3) * 0x8))
+
+/* Miscellaneous Registers (read/write): */
+#define S626_LP_MISC1 0x0088 /* Read/write Misc1. */
+#define S626_LP_WRMISC2 0x0090 /* Write Misc2. */
+#define S626_LP_RDMISC2 0x0082 /* Read Misc2. */
+
+/* Bit masks for MISC1 register that are the same for reads and writes. */
+#define S626_MISC1_WENABLE 0x8000 /* enab writes to MISC2 (except Clear
+ * Watchdog bit). */
+#define S626_MISC1_WDISABLE 0x0000 /* Disable writes to MISC2. */
+#define S626_MISC1_EDCAP 0x1000 /* Enable edge capture on DIO chans
+ * specified by S626_LP_WRCAPSELx. */
+#define S626_MISC1_NOEDCAP 0x0000 /* Disable edge capture on specified
+ * DIO chans. */
+
+/* Bit masks for MISC1 register reads. */
+#define S626_RDMISC1_WDTIMEOUT 0x4000 /* Watchdog timer timed out. */
+
+/* Bit masks for MISC2 register writes. */
+#define S626_WRMISC2_WDCLEAR 0x8000 /* Reset watchdog timer to zero. */
+#define S626_WRMISC2_CHARGE_ENABLE 0x4000 /* Enable battery trickle charging. */
+
+/* Bit masks for MISC2 register that are the same for reads and writes. */
+#define S626_MISC2_BATT_ENABLE 0x0008 /* Backup battery enable. */
+#define S626_MISC2_WDENABLE 0x0004 /* Watchdog timer enable. */
+#define S626_MISC2_WDPERIOD_MASK 0x0003 /* Watchdog interval select mask. */
+
+/* Bit masks for ACON1 register. */
+#define S626_A2_RUN 0x40000000 /* Run A2 based on TSL2. */
+#define S626_A1_RUN 0x20000000 /* Run A1 based on TSL1. */
+#define S626_A1_SWAP 0x00200000 /* Use big-endian for A1. */
+#define S626_A2_SWAP 0x00100000 /* Use big-endian for A2. */
+#define S626_WS_MODES 0x00019999 /* WS0 = TSL1 trigger input,
+ * WS1-WS4 = CS* outputs. */
+
+#if S626_PLATFORM == S626_INTEL /* Base ACON1 config: always run
+ * A1 based on TSL1. */
+#define S626_ACON1_BASE (S626_WS_MODES | S626_A1_RUN)
+#elif S626_PLATFORM == S626_MOTOROLA
+#define S626_ACON1_BASE \
+ (S626_WS_MODES | S626_A1_RUN | S626_A1_SWAP | S626_A2_SWAP)
+#endif
+
+#define S626_ACON1_ADCSTART S626_ACON1_BASE /* Start ADC: run A1
+ * based on TSL1. */
+#define S626_ACON1_DACSTART (S626_ACON1_BASE | S626_A2_RUN)
+/* Start transmit to DAC: run A2 based on TSL2. */
+#define S626_ACON1_DACSTOP S626_ACON1_BASE /* Halt A2. */
+
+/* Bit masks for ACON2 register. */
+#define S626_A1_CLKSRC_BCLK1 0x00000000 /* A1 bit rate = BCLK1 (ADC). */
+#define S626_A2_CLKSRC_X1 0x00800000 /* A2 bit rate = ACLK/1
+ * (DACs). */
+#define S626_A2_CLKSRC_X2 0x00C00000 /* A2 bit rate = ACLK/2
+ * (DACs). */
+#define S626_A2_CLKSRC_X4 0x01400000 /* A2 bit rate = ACLK/4
+ * (DACs). */
+#define S626_INVERT_BCLK2 0x00100000 /* Invert BCLK2 (DACs). */
+#define S626_BCLK2_OE 0x00040000 /* Enable BCLK2 (DACs). */
+#define S626_ACON2_XORMASK 0x000C0000 /* XOR mask for ACON2
+ * active-low bits. */
+
+#define S626_ACON2_INIT (S626_ACON2_XORMASK ^ \
+ (S626_A1_CLKSRC_BCLK1 | S626_A2_CLKSRC_X2 | \
+ S626_INVERT_BCLK2 | S626_BCLK2_OE))
+
+/* Bit masks for timeslot records. */
+#define S626_WS1 0x40000000 /* WS output to assert. */
+#define S626_WS2 0x20000000
+#define S626_WS3 0x10000000
+#define S626_WS4 0x08000000
+#define S626_RSD1 0x01000000 /* Shift A1 data in on SD1. */
+#define S626_SDW_A1 0x00800000 /* Store rcv'd char at next char
+ * slot of DWORD1 buffer. */
+#define S626_SIB_A1 0x00400000 /* Store rcv'd char at next
+ * char slot of FB1 buffer. */
+#define S626_SF_A1 0x00200000 /* Write unsigned long
+ * buffer to input FIFO. */
+
+/* Select parallel-to-serial converter's data source: */
+#define S626_XFIFO_0 0x00000000 /* Data fifo byte 0. */
+#define S626_XFIFO_1 0x00000010 /* Data fifo byte 1. */
+#define S626_XFIFO_2 0x00000020 /* Data fifo byte 2. */
+#define S626_XFIFO_3 0x00000030 /* Data fifo byte 3. */
+#define S626_XFB0 0x00000040 /* FB_BUFFER byte 0. */
+#define S626_XFB1 0x00000050 /* FB_BUFFER byte 1. */
+#define S626_XFB2 0x00000060 /* FB_BUFFER byte 2. */
+#define S626_XFB3 0x00000070 /* FB_BUFFER byte 3. */
+#define S626_SIB_A2 0x00000200 /* Store next dword from A2's
+ * input shifter to FB2
+ * buffer. */
+#define S626_SF_A2 0x00000100 /* Store next dword from A2's
+ * input shifter to its input
+ * fifo. */
+#define S626_LF_A2 0x00000080 /* Load next dword from A2's
+ * output fifo into its
+ * output dword buffer. */
+#define S626_XSD2 0x00000008 /* Shift data out on SD2. */
+#define S626_RSD3 0x00001800 /* Shift data in on SD3. */
+#define S626_RSD2 0x00001000 /* Shift data in on SD2. */
+#define S626_LOW_A2 0x00000002 /* Drive last SD low for 7 clks,
+ * then tri-state. */
+#define S626_EOS 0x00000001 /* End of superframe. */
+
+/* I2C configuration constants. */
+#define S626_I2C_CLKSEL 0x0400 /* I2C bit rate =
+ * PCIclk/480 = 68.75 KHz. */
+#define S626_I2C_BITRATE 68.75 /* I2C bus data bit rate
+ * (determined by
+ * S626_I2C_CLKSEL) in KHz. */
+#define S626_I2C_WRTIME 15.0 /* Worst case time, in msec,
+ * for EEPROM internal write
+ * op. */
+
+/* I2C manifest constants. */
+
+/* Max retries to wait for EEPROM write. */
+#define S626_I2C_RETRIES (S626_I2C_WRTIME * S626_I2C_BITRATE / 9.0)
+#define S626_I2C_ERR 0x0002 /* I2C control/status flag ERROR. */
+#define S626_I2C_BUSY 0x0001 /* I2C control/status flag BUSY. */
+#define S626_I2C_ABORT 0x0080 /* I2C status flag ABORT. */
+#define S626_I2C_ATTRSTART 0x3 /* I2C attribute START. */
+#define S626_I2C_ATTRCONT 0x2 /* I2C attribute CONT. */
+#define S626_I2C_ATTRSTOP 0x1 /* I2C attribute STOP. */
+#define S626_I2C_ATTRNOP 0x0 /* I2C attribute NOP. */
+
+/* Code macros used for constructing I2C command bytes. */
+#define S626_I2C_B2(ATTR, VAL) (((ATTR) << 6) | ((VAL) << 24))
+#define S626_I2C_B1(ATTR, VAL) (((ATTR) << 4) | ((VAL) << 16))
+#define S626_I2C_B0(ATTR, VAL) (((ATTR) << 2) | ((VAL) << 8))
+
+/* DEBI command constants. */
+#define S626_DEBI_CMD_SIZE16 (2 << 17) /* Transfer size is always
+ * 2 bytes. */
+#define S626_DEBI_CMD_READ 0x00010000 /* Read operation. */
+#define S626_DEBI_CMD_WRITE 0x00000000 /* Write operation. */
+
+/* Read immediate 2 bytes. */
+#define S626_DEBI_CMD_RDWORD (S626_DEBI_CMD_READ | S626_DEBI_CMD_SIZE16)
+
+/* Write immediate 2 bytes. */
+#define S626_DEBI_CMD_WRWORD (S626_DEBI_CMD_WRITE | S626_DEBI_CMD_SIZE16)
+
+/* DEBI configuration constants. */
+#define S626_DEBI_CFG_XIRQ_EN 0x80000000 /* Enable external interrupt
+ * on GPIO3. */
+#define S626_DEBI_CFG_XRESUME 0x40000000 /* Resume block */
+ /* Transfer when XIRQ
+ * deasserted. */
+#define S626_DEBI_CFG_TOQ 0x03C00000 /* Timeout (15 PCI cycles). */
+#define S626_DEBI_CFG_FAST 0x10000000 /* Fast mode enable. */
+
+/* 4-bit field that specifies DEBI timeout value in PCI clock cycles: */
+#define S626_DEBI_CFG_TOUT_BIT 22 /* Finish DEBI cycle after this many
+ * clocks. */
+
+/* 2-bit field that specifies Endian byte lane steering: */
+#define S626_DEBI_CFG_SWAP_NONE 0x00000000 /* Straight - don't swap any
+ * bytes (Intel). */
+#define S626_DEBI_CFG_SWAP_2 0x00100000 /* 2-byte swap (Motorola). */
+#define S626_DEBI_CFG_SWAP_4 0x00200000 /* 4-byte swap. */
+#define S626_DEBI_CFG_SLAVE16 0x00080000 /* Slave is able to serve
+ * 16-bit cycles. */
+#define S626_DEBI_CFG_INC 0x00040000 /* Enable address increment
+ * for block transfers. */
+#define S626_DEBI_CFG_INTEL 0x00020000 /* Intel style local bus. */
+#define S626_DEBI_CFG_TIMEROFF 0x00010000 /* Disable timer. */
+
+#if S626_PLATFORM == S626_INTEL
+
+#define S626_DEBI_TOUT 7 /* Wait 7 PCI clocks (212 ns) before
+ * polling RDY. */
+
+/* Intel byte lane steering (pass through all byte lanes). */
+#define S626_DEBI_SWAP S626_DEBI_CFG_SWAP_NONE
+
+#elif S626_PLATFORM == S626_MOTOROLA
+
+#define S626_DEBI_TOUT 15 /* Wait 15 PCI clocks (454 ns) maximum
+ * before timing out. */
+
+/* Motorola byte lane steering. */
+#define S626_DEBI_SWAP S626_DEBI_CFG_SWAP_2
+
+#endif
+
+/* DEBI page table constants. */
+#define S626_DEBI_PAGE_DISABLE 0x00000000 /* Paging disable. */
+
+/* ******* EXTRA FROM OTHER SENSORAY * .h ******* */
+
+/* LoadSrc values: */
+#define S626_LOADSRC_INDX 0 /* Preload core in response to Index. */
+#define S626_LOADSRC_OVER 1 /* Preload core in response to
+ * Overflow. */
+#define S626_LOADSRCB_OVERA 2 /* Preload B core in response to
+ * A Overflow. */
+#define S626_LOADSRC_NONE 3 /* Never preload core. */
+
+/* IntSrc values: */
+#define S626_INTSRC_NONE 0 /* Interrupts disabled. */
+#define S626_INTSRC_OVER 1 /* Interrupt on Overflow. */
+#define S626_INTSRC_INDX 2 /* Interrupt on Index. */
+#define S626_INTSRC_BOTH 3 /* Interrupt on Index or Overflow. */
+
+/* LatchSrc values: */
+#define S626_LATCHSRC_AB_READ 0 /* Latch on read. */
+#define S626_LATCHSRC_A_INDXA 1 /* Latch A on A Index. */
+#define S626_LATCHSRC_B_INDXB 2 /* Latch B on B Index. */
+#define S626_LATCHSRC_B_OVERA 3 /* Latch B on A Overflow. */
+
+/* IndxSrc values: */
+#define S626_INDXSRC_ENCODER 0 /* Encoder. */
+#define S626_INDXSRC_DIGIN 1 /* Digital inputs. */
+#define S626_INDXSRC_SOFT 2 /* S/w controlled by IndxPol bit. */
+#define S626_INDXSRC_DISABLED 3 /* Index disabled. */
+
+/* IndxPol values: */
+#define S626_INDXPOL_POS 0 /* Index input is active high. */
+#define S626_INDXPOL_NEG 1 /* Index input is active low. */
+
+/* Logical encoder mode values: */
+#define S626_ENCMODE_COUNTER 0 /* Counter mode. */
+#define S626_ENCMODE_TIMER 2 /* Timer mode. */
+#define S626_ENCMODE_EXTENDER 3 /* Extender mode. */
+
+/* Physical CntSrc values (for Counter A source and Counter B source): */
+#define S626_CNTSRC_ENCODER 0 /* Encoder */
+#define S626_CNTSRC_DIGIN 1 /* Digital inputs */
+#define S626_CNTSRC_SYSCLK 2 /* System clock up */
+#define S626_CNTSRC_SYSCLK_DOWN 3 /* System clock down */
+
+/* ClkPol values: */
+#define S626_CLKPOL_POS 0 /* Counter/Extender clock is
+ * active high. */
+#define S626_CLKPOL_NEG 1 /* Counter/Extender clock is
+ * active low. */
+#define S626_CNTDIR_UP 0 /* Timer counts up. */
+#define S626_CNTDIR_DOWN 1 /* Timer counts down. */
+
+/* ClkEnab values: */
+#define S626_CLKENAB_ALWAYS 0 /* Clock always enabled. */
+#define S626_CLKENAB_INDEX 1 /* Clock is enabled by index. */
+
+/* ClkMult values: */
+#define S626_CLKMULT_4X 0 /* 4x clock multiplier. */
+#define S626_CLKMULT_2X 1 /* 2x clock multiplier. */
+#define S626_CLKMULT_1X 2 /* 1x clock multiplier. */
+#define S626_CLKMULT_SPECIAL 3 /* Special clock multiplier value. */
+
+/* Sanity-check limits for parameters. */
+
+#define S626_NUM_COUNTERS 6 /* Maximum valid counter
+ * logical channel number. */
+#define S626_NUM_INTSOURCES 4
+#define S626_NUM_LATCHSOURCES 4
+#define S626_NUM_CLKMULTS 4
+#define S626_NUM_CLKSOURCES 4
+#define S626_NUM_CLKPOLS 2
+#define S626_NUM_INDEXPOLS 2
+#define S626_NUM_INDEXSOURCES 2
+#define S626_NUM_LOADTRIGS 4
+
+/* General macros for manipulating bitfields: */
+#define S626_MAKE(x, w, p) (((x) & ((1 << (w)) - 1)) << (p))
+#define S626_UNMAKE(v, w, p) (((v) >> (p)) & ((1 << (w)) - 1))
+
+/* Bit field positions in CRA: */
+#define S626_CRABIT_INDXSRC_B 14 /* B index source. */
+#define S626_CRABIT_CNTSRC_B 12 /* B counter source. */
+#define S626_CRABIT_INDXPOL_A 11 /* A index polarity. */
+#define S626_CRABIT_LOADSRC_A 9 /* A preload trigger. */
+#define S626_CRABIT_CLKMULT_A 7 /* A clock multiplier. */
+#define S626_CRABIT_INTSRC_A 5 /* A interrupt source. */
+#define S626_CRABIT_CLKPOL_A 4 /* A clock polarity. */
+#define S626_CRABIT_INDXSRC_A 2 /* A index source. */
+#define S626_CRABIT_CNTSRC_A 0 /* A counter source. */
+
+/* Bit field widths in CRA: */
+#define S626_CRAWID_INDXSRC_B 2
+#define S626_CRAWID_CNTSRC_B 2
+#define S626_CRAWID_INDXPOL_A 1
+#define S626_CRAWID_LOADSRC_A 2
+#define S626_CRAWID_CLKMULT_A 2
+#define S626_CRAWID_INTSRC_A 2
+#define S626_CRAWID_CLKPOL_A 1
+#define S626_CRAWID_INDXSRC_A 2
+#define S626_CRAWID_CNTSRC_A 2
+
+/* Bit field masks for CRA: */
+#define S626_CRAMSK_INDXSRC_B S626_SET_CRA_INDXSRC_B(~0)
+#define S626_CRAMSK_CNTSRC_B S626_SET_CRA_CNTSRC_B(~0)
+#define S626_CRAMSK_INDXPOL_A S626_SET_CRA_INDXPOL_A(~0)
+#define S626_CRAMSK_LOADSRC_A S626_SET_CRA_LOADSRC_A(~0)
+#define S626_CRAMSK_CLKMULT_A S626_SET_CRA_CLKMULT_A(~0)
+#define S626_CRAMSK_INTSRC_A S626_SET_CRA_INTSRC_A(~0)
+#define S626_CRAMSK_CLKPOL_A S626_SET_CRA_CLKPOL_A(~0)
+#define S626_CRAMSK_INDXSRC_A S626_SET_CRA_INDXSRC_A(~0)
+#define S626_CRAMSK_CNTSRC_A S626_SET_CRA_CNTSRC_A(~0)
+
+/* Construct parts of the CRA value: */
+#define S626_SET_CRA_INDXSRC_B(x) \
+ S626_MAKE((x), S626_CRAWID_INDXSRC_B, S626_CRABIT_INDXSRC_B)
+#define S626_SET_CRA_CNTSRC_B(x) \
+ S626_MAKE((x), S626_CRAWID_CNTSRC_B, S626_CRABIT_CNTSRC_B)
+#define S626_SET_CRA_INDXPOL_A(x) \
+ S626_MAKE((x), S626_CRAWID_INDXPOL_A, S626_CRABIT_INDXPOL_A)
+#define S626_SET_CRA_LOADSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_LOADSRC_A, S626_CRABIT_LOADSRC_A)
+#define S626_SET_CRA_CLKMULT_A(x) \
+ S626_MAKE((x), S626_CRAWID_CLKMULT_A, S626_CRABIT_CLKMULT_A)
+#define S626_SET_CRA_INTSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_INTSRC_A, S626_CRABIT_INTSRC_A)
+#define S626_SET_CRA_CLKPOL_A(x) \
+ S626_MAKE((x), S626_CRAWID_CLKPOL_A, S626_CRABIT_CLKPOL_A)
+#define S626_SET_CRA_INDXSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_INDXSRC_A, S626_CRABIT_INDXSRC_A)
+#define S626_SET_CRA_CNTSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_CNTSRC_A, S626_CRABIT_CNTSRC_A)
+
+/* Extract parts of the CRA value: */
+#define S626_GET_CRA_INDXSRC_B(v) \
+ S626_UNMAKE((v), S626_CRAWID_INDXSRC_B, S626_CRABIT_INDXSRC_B)
+#define S626_GET_CRA_CNTSRC_B(v) \
+ S626_UNMAKE((v), S626_CRAWID_CNTSRC_B, S626_CRABIT_CNTSRC_B)
+#define S626_GET_CRA_INDXPOL_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_INDXPOL_A, S626_CRABIT_INDXPOL_A)
+#define S626_GET_CRA_LOADSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_LOADSRC_A, S626_CRABIT_LOADSRC_A)
+#define S626_GET_CRA_CLKMULT_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_CLKMULT_A, S626_CRABIT_CLKMULT_A)
+#define S626_GET_CRA_INTSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_INTSRC_A, S626_CRABIT_INTSRC_A)
+#define S626_GET_CRA_CLKPOL_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_CLKPOL_A, S626_CRABIT_CLKPOL_A)
+#define S626_GET_CRA_INDXSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_INDXSRC_A, S626_CRABIT_INDXSRC_A)
+#define S626_GET_CRA_CNTSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_CNTSRC_A, S626_CRABIT_CNTSRC_A)
+
+/* Bit field positions in CRB: */
+#define S626_CRBBIT_INTRESETCMD 15 /* (w) Interrupt reset command. */
+#define S626_CRBBIT_CNTDIR_B 15 /* (r) B counter direction. */
+#define S626_CRBBIT_INTRESET_B 14 /* (w) B interrupt reset enable. */
+#define S626_CRBBIT_OVERDO_A 14 /* (r) A overflow routed to dig. out. */
+#define S626_CRBBIT_INTRESET_A 13 /* (w) A interrupt reset enable. */
+#define S626_CRBBIT_OVERDO_B 13 /* (r) B overflow routed to dig. out. */
+#define S626_CRBBIT_CLKENAB_A 12 /* A clock enable. */
+#define S626_CRBBIT_INTSRC_B 10 /* B interrupt source. */
+#define S626_CRBBIT_LATCHSRC 8 /* A/B latch source. */
+#define S626_CRBBIT_LOADSRC_B 6 /* B preload trigger. */
+#define S626_CRBBIT_CLEAR_B 7 /* B cleared when A overflows. */
+#define S626_CRBBIT_CLKMULT_B 3 /* B clock multiplier. */
+#define S626_CRBBIT_CLKENAB_B 2 /* B clock enable. */
+#define S626_CRBBIT_INDXPOL_B 1 /* B index polarity. */
+#define S626_CRBBIT_CLKPOL_B 0 /* B clock polarity. */
+
+/* Bit field widths in CRB: */
+#define S626_CRBWID_INTRESETCMD 1
+#define S626_CRBWID_CNTDIR_B 1
+#define S626_CRBWID_INTRESET_B 1
+#define S626_CRBWID_OVERDO_A 1
+#define S626_CRBWID_INTRESET_A 1
+#define S626_CRBWID_OVERDO_B 1
+#define S626_CRBWID_CLKENAB_A 1
+#define S626_CRBWID_INTSRC_B 2
+#define S626_CRBWID_LATCHSRC 2
+#define S626_CRBWID_LOADSRC_B 2
+#define S626_CRBWID_CLEAR_B 1
+#define S626_CRBWID_CLKMULT_B 2
+#define S626_CRBWID_CLKENAB_B 1
+#define S626_CRBWID_INDXPOL_B 1
+#define S626_CRBWID_CLKPOL_B 1
+
+/* Bit field masks for CRB: */
+#define S626_CRBMSK_INTRESETCMD S626_SET_CRB_INTRESETCMD(~0) /* (w) */
+#define S626_CRBMSK_CNTDIR_B S626_CRBMSK_INTRESETCMD /* (r) */
+#define S626_CRBMSK_INTRESET_B S626_SET_CRB_INTRESET_B(~0) /* (w) */
+#define S626_CRBMSK_OVERDO_A S626_CRBMSK_INTRESET_B /* (r) */
+#define S626_CRBMSK_INTRESET_A S626_SET_CRB_INTRESET_A(~0) /* (w) */
+#define S626_CRBMSK_OVERDO_B S626_CRBMSK_INTRESET_A /* (r) */
+#define S626_CRBMSK_CLKENAB_A S626_SET_CRB_CLKENAB_A(~0)
+#define S626_CRBMSK_INTSRC_B S626_SET_CRB_INTSRC_B(~0)
+#define S626_CRBMSK_LATCHSRC S626_SET_CRB_LATCHSRC(~0)
+#define S626_CRBMSK_LOADSRC_B S626_SET_CRB_LOADSRC_B(~0)
+#define S626_CRBMSK_CLEAR_B S626_SET_CRB_CLEAR_B(~0)
+#define S626_CRBMSK_CLKMULT_B S626_SET_CRB_CLKMULT_B(~0)
+#define S626_CRBMSK_CLKENAB_B S626_SET_CRB_CLKENAB_B(~0)
+#define S626_CRBMSK_INDXPOL_B S626_SET_CRB_INDXPOL_B(~0)
+#define S626_CRBMSK_CLKPOL_B S626_SET_CRB_CLKPOL_B(~0)
+
+/* Interrupt reset control bits. */
+#define S626_CRBMSK_INTCTRL (S626_CRBMSK_INTRESETCMD | \
+ S626_CRBMSK_INTRESET_A | \
+ S626_CRBMSK_INTRESET_B)
+
+/* Construct parts of the CRB value: */
+#define S626_SET_CRB_INTRESETCMD(x) \
+ S626_MAKE((x), S626_CRBWID_INTRESETCMD, S626_CRBBIT_INTRESETCMD)
+#define S626_SET_CRB_INTRESET_B(x) \
+ S626_MAKE((x), S626_CRBWID_INTRESET_B, S626_CRBBIT_INTRESET_B)
+#define S626_SET_CRB_INTRESET_A(x) \
+ S626_MAKE((x), S626_CRBWID_INTRESET_A, S626_CRBBIT_INTRESET_A)
+#define S626_SET_CRB_CLKENAB_A(x) \
+ S626_MAKE((x), S626_CRBWID_CLKENAB_A, S626_CRBBIT_CLKENAB_A)
+#define S626_SET_CRB_INTSRC_B(x) \
+ S626_MAKE((x), S626_CRBWID_INTSRC_B, S626_CRBBIT_INTSRC_B)
+#define S626_SET_CRB_LATCHSRC(x) \
+ S626_MAKE((x), S626_CRBWID_LATCHSRC, S626_CRBBIT_LATCHSRC)
+#define S626_SET_CRB_LOADSRC_B(x) \
+ S626_MAKE((x), S626_CRBWID_LOADSRC_B, S626_CRBBIT_LOADSRC_B)
+#define S626_SET_CRB_CLEAR_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLEAR_B, S626_CRBBIT_CLEAR_B)
+#define S626_SET_CRB_CLKMULT_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLKMULT_B, S626_CRBBIT_CLKMULT_B)
+#define S626_SET_CRB_CLKENAB_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLKENAB_B, S626_CRBBIT_CLKENAB_B)
+#define S626_SET_CRB_INDXPOL_B(x) \
+ S626_MAKE((x), S626_CRBWID_INDXPOL_B, S626_CRBBIT_INDXPOL_B)
+#define S626_SET_CRB_CLKPOL_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLKPOL_B, S626_CRBBIT_CLKPOL_B)
+
+/* Extract parts of the CRB value: */
+#define S626_GET_CRB_CNTDIR_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CNTDIR_B, S626_CRBBIT_CNTDIR_B)
+#define S626_GET_CRB_OVERDO_A(v) \
+ S626_UNMAKE((v), S626_CRBWID_OVERDO_A, S626_CRBBIT_OVERDO_A)
+#define S626_GET_CRB_OVERDO_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_OVERDO_B, S626_CRBBIT_OVERDO_B)
+#define S626_GET_CRB_CLKENAB_A(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKENAB_A, S626_CRBBIT_CLKENAB_A)
+#define S626_GET_CRB_INTSRC_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_INTSRC_B, S626_CRBBIT_INTSRC_B)
+#define S626_GET_CRB_LATCHSRC(v) \
+ S626_UNMAKE((v), S626_CRBWID_LATCHSRC, S626_CRBBIT_LATCHSRC)
+#define S626_GET_CRB_LOADSRC_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_LOADSRC_B, S626_CRBBIT_LOADSRC_B)
+#define S626_GET_CRB_CLEAR_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLEAR_B, S626_CRBBIT_CLEAR_B)
+#define S626_GET_CRB_CLKMULT_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKMULT_B, S626_CRBBIT_CLKMULT_B)
+#define S626_GET_CRB_CLKENAB_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKENAB_B, S626_CRBBIT_CLKENAB_B)
+#define S626_GET_CRB_INDXPOL_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_INDXPOL_B, S626_CRBBIT_INDXPOL_B)
+#define S626_GET_CRB_CLKPOL_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKPOL_B, S626_CRBBIT_CLKPOL_B)
+
+/* Bit field positions for standardized SETUP structure: */
+#define S626_STDBIT_INTSRC 13
+#define S626_STDBIT_LATCHSRC 11
+#define S626_STDBIT_LOADSRC 9
+#define S626_STDBIT_INDXSRC 7
+#define S626_STDBIT_INDXPOL 6
+#define S626_STDBIT_ENCMODE 4
+#define S626_STDBIT_CLKPOL 3
+#define S626_STDBIT_CLKMULT 1
+#define S626_STDBIT_CLKENAB 0
+
+/* Bit field widths for standardized SETUP structure: */
+#define S626_STDWID_INTSRC 2
+#define S626_STDWID_LATCHSRC 2
+#define S626_STDWID_LOADSRC 2
+#define S626_STDWID_INDXSRC 2
+#define S626_STDWID_INDXPOL 1
+#define S626_STDWID_ENCMODE 2
+#define S626_STDWID_CLKPOL 1
+#define S626_STDWID_CLKMULT 2
+#define S626_STDWID_CLKENAB 1
+
+/* Bit field masks for standardized SETUP structure: */
+#define S626_STDMSK_INTSRC S626_SET_STD_INTSRC(~0)
+#define S626_STDMSK_LATCHSRC S626_SET_STD_LATCHSRC(~0)
+#define S626_STDMSK_LOADSRC S626_SET_STD_LOADSRC(~0)
+#define S626_STDMSK_INDXSRC S626_SET_STD_INDXSRC(~0)
+#define S626_STDMSK_INDXPOL S626_SET_STD_INDXPOL(~0)
+#define S626_STDMSK_ENCMODE S626_SET_STD_ENCMODE(~0)
+#define S626_STDMSK_CLKPOL S626_SET_STD_CLKPOL(~0)
+#define S626_STDMSK_CLKMULT S626_SET_STD_CLKMULT(~0)
+#define S626_STDMSK_CLKENAB S626_SET_STD_CLKENAB(~0)
+
+/* Construct parts of standardized SETUP structure: */
+#define S626_SET_STD_INTSRC(x) \
+ S626_MAKE((x), S626_STDWID_INTSRC, S626_STDBIT_INTSRC)
+#define S626_SET_STD_LATCHSRC(x) \
+ S626_MAKE((x), S626_STDWID_LATCHSRC, S626_STDBIT_LATCHSRC)
+#define S626_SET_STD_LOADSRC(x) \
+ S626_MAKE((x), S626_STDWID_LOADSRC, S626_STDBIT_LOADSRC)
+#define S626_SET_STD_INDXSRC(x) \
+ S626_MAKE((x), S626_STDWID_INDXSRC, S626_STDBIT_INDXSRC)
+#define S626_SET_STD_INDXPOL(x) \
+ S626_MAKE((x), S626_STDWID_INDXPOL, S626_STDBIT_INDXPOL)
+#define S626_SET_STD_ENCMODE(x) \
+ S626_MAKE((x), S626_STDWID_ENCMODE, S626_STDBIT_ENCMODE)
+#define S626_SET_STD_CLKPOL(x) \
+ S626_MAKE((x), S626_STDWID_CLKPOL, S626_STDBIT_CLKPOL)
+#define S626_SET_STD_CLKMULT(x) \
+ S626_MAKE((x), S626_STDWID_CLKMULT, S626_STDBIT_CLKMULT)
+#define S626_SET_STD_CLKENAB(x) \
+ S626_MAKE((x), S626_STDWID_CLKENAB, S626_STDBIT_CLKENAB)
+
+/* Extract parts of standardized SETUP structure: */
+#define S626_GET_STD_INTSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_INTSRC, S626_STDBIT_INTSRC)
+#define S626_GET_STD_LATCHSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_LATCHSRC, S626_STDBIT_LATCHSRC)
+#define S626_GET_STD_LOADSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_LOADSRC, S626_STDBIT_LOADSRC)
+#define S626_GET_STD_INDXSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_INDXSRC, S626_STDBIT_INDXSRC)
+#define S626_GET_STD_INDXPOL(v) \
+ S626_UNMAKE((v), S626_STDWID_INDXPOL, S626_STDBIT_INDXPOL)
+#define S626_GET_STD_ENCMODE(v) \
+ S626_UNMAKE((v), S626_STDWID_ENCMODE, S626_STDBIT_ENCMODE)
+#define S626_GET_STD_CLKPOL(v) \
+ S626_UNMAKE((v), S626_STDWID_CLKPOL, S626_STDBIT_CLKPOL)
+#define S626_GET_STD_CLKMULT(v) \
+ S626_UNMAKE((v), S626_STDWID_CLKMULT, S626_STDBIT_CLKMULT)
+#define S626_GET_STD_CLKENAB(v) \
+ S626_UNMAKE((v), S626_STDWID_CLKENAB, S626_STDBIT_CLKENAB)
+
+#endif
diff --git a/drivers/staging/comedi/drivers/serial2002.c b/drivers/staging/comedi/drivers/serial2002.c
new file mode 100644
index 000000000..304ebff11
--- /dev/null
+++ b/drivers/staging/comedi/drivers/serial2002.c
@@ -0,0 +1,798 @@
+/*
+ comedi/drivers/serial2002.c
+ Skeleton code for a Comedi driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2002 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+/*
+Driver: serial2002
+Description: Driver for serial connected hardware
+Devices:
+Author: Anders Blomdell
+Updated: Fri, 7 Jun 2002 12:56:45 -0700
+Status: in development
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <linux/termios.h>
+#include <asm/ioctls.h>
+#include <linux/serial.h>
+#include <linux/poll.h>
+
+struct serial2002_range_table_t {
+ /* HACK... */
+ int length;
+ struct comedi_krange range;
+};
+
+struct serial2002_private {
+ int port; /* /dev/ttyS<port> */
+ int speed; /* baudrate */
+ struct file *tty;
+ unsigned int ao_readback[32];
+ unsigned char digital_in_mapping[32];
+ unsigned char digital_out_mapping[32];
+ unsigned char analog_in_mapping[32];
+ unsigned char analog_out_mapping[32];
+ unsigned char encoder_in_mapping[32];
+ struct serial2002_range_table_t in_range[32], out_range[32];
+};
+
+struct serial_data {
+ enum { is_invalid, is_digital, is_channel } kind;
+ int index;
+ unsigned long value;
+};
+
+/*
+ * The configuration serial_data.value read from the device is
+ * a bitmask that defines specific options of a channel:
+ *
+ * 4:0 - the channel to configure
+ * 7:5 - the kind of channel
+ * 9:8 - the command used to configure the channel
+ *
+ * The remaining bits vary in use depending on the command:
+ *
+ * BITS 15:10 - the channel bits (maxdata)
+ * MIN/MAX 12:10 - the units multiplier for the scale
+ * 13 - the sign of the scale
+ * 33:14 - the base value for the range
+ */
+#define S2002_CFG_CHAN(x) ((x) & 0x1f)
+#define S2002_CFG_KIND(x) (((x) >> 5) & 0x7)
+#define S2002_CFG_KIND_INVALID 0
+#define S2002_CFG_KIND_DIGITAL_IN 1
+#define S2002_CFG_KIND_DIGITAL_OUT 2
+#define S2002_CFG_KIND_ANALOG_IN 3
+#define S2002_CFG_KIND_ANALOG_OUT 4
+#define S2002_CFG_KIND_ENCODER_IN 5
+#define S2002_CFG_CMD(x) (((x) >> 8) & 0x3)
+#define S2002_CFG_CMD_BITS 0
+#define S2002_CFG_CMD_MIN 1
+#define S2002_CFG_CMD_MAX 2
+#define S2002_CFG_BITS(x) (((x) >> 10) & 0x3f)
+#define S2002_CFG_UNITS(x) (((x) >> 10) & 0x7)
+#define S2002_CFG_SIGN(x) (((x) >> 13) & 0x1)
+#define S2002_CFG_BASE(x) (((x) >> 14) & 0xfffff)
+
+static long serial2002_tty_ioctl(struct file *f, unsigned op,
+ unsigned long param)
+{
+ if (f->f_op->unlocked_ioctl)
+ return f->f_op->unlocked_ioctl(f, op, param);
+
+ return -ENOSYS;
+}
+
+static int serial2002_tty_write(struct file *f, unsigned char *buf, int count)
+{
+ const char __user *p = (__force const char __user *)buf;
+ int result;
+ loff_t offset = 0;
+ mm_segment_t oldfs;
+
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
+ result = __vfs_write(f, p, count, &offset);
+ set_fs(oldfs);
+ return result;
+}
+
+static void serial2002_tty_read_poll_wait(struct file *f, int timeout)
+{
+ struct poll_wqueues table;
+ struct timeval start, now;
+
+ do_gettimeofday(&start);
+ poll_initwait(&table);
+ while (1) {
+ long elapsed;
+ int mask;
+
+ mask = f->f_op->poll(f, &table.pt);
+ if (mask & (POLLRDNORM | POLLRDBAND | POLLIN |
+ POLLHUP | POLLERR)) {
+ break;
+ }
+ do_gettimeofday(&now);
+ elapsed = 1000000 * (now.tv_sec - start.tv_sec) +
+ now.tv_usec - start.tv_usec;
+ if (elapsed > timeout)
+ break;
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(((timeout - elapsed) * HZ) / 10000);
+ }
+ poll_freewait(&table);
+}
+
+static int serial2002_tty_read(struct file *f, int timeout)
+{
+ unsigned char ch;
+ int result;
+
+ result = -1;
+ if (!IS_ERR(f)) {
+ mm_segment_t oldfs;
+ char __user *p = (__force char __user *)&ch;
+ loff_t offset = 0;
+
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
+ if (f->f_op->poll) {
+ serial2002_tty_read_poll_wait(f, timeout);
+
+ if (__vfs_read(f, p, 1, &offset) == 1)
+ result = ch;
+ } else {
+ /* Device does not support poll, busy wait */
+ int retries = 0;
+
+ while (1) {
+ retries++;
+ if (retries >= timeout)
+ break;
+
+ if (__vfs_read(f, p, 1, &offset) == 1) {
+ result = ch;
+ break;
+ }
+ udelay(100);
+ }
+ }
+ set_fs(oldfs);
+ }
+ return result;
+}
+
+static void serial2002_tty_setspeed(struct file *f, int speed)
+{
+ struct termios termios;
+ struct serial_struct serial;
+ mm_segment_t oldfs;
+
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
+
+ /* Set speed */
+ serial2002_tty_ioctl(f, TCGETS, (unsigned long)&termios);
+ termios.c_iflag = 0;
+ termios.c_oflag = 0;
+ termios.c_lflag = 0;
+ termios.c_cflag = CLOCAL | CS8 | CREAD;
+ termios.c_cc[VMIN] = 0;
+ termios.c_cc[VTIME] = 0;
+ switch (speed) {
+ case 2400:
+ termios.c_cflag |= B2400;
+ break;
+ case 4800:
+ termios.c_cflag |= B4800;
+ break;
+ case 9600:
+ termios.c_cflag |= B9600;
+ break;
+ case 19200:
+ termios.c_cflag |= B19200;
+ break;
+ case 38400:
+ termios.c_cflag |= B38400;
+ break;
+ case 57600:
+ termios.c_cflag |= B57600;
+ break;
+ case 115200:
+ termios.c_cflag |= B115200;
+ break;
+ default:
+ termios.c_cflag |= B9600;
+ break;
+ }
+ serial2002_tty_ioctl(f, TCSETS, (unsigned long)&termios);
+
+ /* Set low latency */
+ serial2002_tty_ioctl(f, TIOCGSERIAL, (unsigned long)&serial);
+ serial.flags |= ASYNC_LOW_LATENCY;
+ serial2002_tty_ioctl(f, TIOCSSERIAL, (unsigned long)&serial);
+
+ set_fs(oldfs);
+}
+
+static void serial2002_poll_digital(struct file *f, int channel)
+{
+ char cmd;
+
+ cmd = 0x40 | (channel & 0x1f);
+ serial2002_tty_write(f, &cmd, 1);
+}
+
+static void serial2002_poll_channel(struct file *f, int channel)
+{
+ char cmd;
+
+ cmd = 0x60 | (channel & 0x1f);
+ serial2002_tty_write(f, &cmd, 1);
+}
+
+static struct serial_data serial2002_read(struct file *f, int timeout)
+{
+ struct serial_data result;
+ int length;
+
+ result.kind = is_invalid;
+ result.index = 0;
+ result.value = 0;
+ length = 0;
+ while (1) {
+ int data = serial2002_tty_read(f, timeout);
+
+ length++;
+ if (data < 0) {
+ break;
+ } else if (data & 0x80) {
+ result.value = (result.value << 7) | (data & 0x7f);
+ } else {
+ if (length == 1) {
+ switch ((data >> 5) & 0x03) {
+ case 0:
+ result.value = 0;
+ result.kind = is_digital;
+ break;
+ case 1:
+ result.value = 1;
+ result.kind = is_digital;
+ break;
+ }
+ } else {
+ result.value =
+ (result.value << 2) | ((data & 0x60) >> 5);
+ result.kind = is_channel;
+ }
+ result.index = data & 0x1f;
+ break;
+ }
+ }
+ return result;
+}
+
+static void serial2002_write(struct file *f, struct serial_data data)
+{
+ if (data.kind == is_digital) {
+ unsigned char ch =
+ ((data.value << 5) & 0x20) | (data.index & 0x1f);
+ serial2002_tty_write(f, &ch, 1);
+ } else {
+ unsigned char ch[6];
+ int i = 0;
+
+ if (data.value >= (1L << 30)) {
+ ch[i] = 0x80 | ((data.value >> 30) & 0x03);
+ i++;
+ }
+ if (data.value >= (1L << 23)) {
+ ch[i] = 0x80 | ((data.value >> 23) & 0x7f);
+ i++;
+ }
+ if (data.value >= (1L << 16)) {
+ ch[i] = 0x80 | ((data.value >> 16) & 0x7f);
+ i++;
+ }
+ if (data.value >= (1L << 9)) {
+ ch[i] = 0x80 | ((data.value >> 9) & 0x7f);
+ i++;
+ }
+ ch[i] = 0x80 | ((data.value >> 2) & 0x7f);
+ i++;
+ ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f);
+ i++;
+ serial2002_tty_write(f, ch, i);
+ }
+}
+
+struct config_t {
+ short int kind;
+ short int bits;
+ int min;
+ int max;
+};
+
+static int serial2002_setup_subdevice(struct comedi_subdevice *s,
+ struct config_t *cfg,
+ struct serial2002_range_table_t *range,
+ unsigned char *mapping,
+ int kind)
+{
+ const struct comedi_lrange **range_table_list = NULL;
+ unsigned int *maxdata_list;
+ int j, chan;
+
+ for (chan = 0, j = 0; j < 32; j++) {
+ if (cfg[j].kind == kind)
+ chan++;
+ }
+ s->n_chan = chan;
+ s->maxdata = 0;
+ kfree(s->maxdata_list);
+ maxdata_list = kmalloc_array(s->n_chan, sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!maxdata_list)
+ return -ENOMEM;
+ s->maxdata_list = maxdata_list;
+ kfree(s->range_table_list);
+ s->range_table = NULL;
+ s->range_table_list = NULL;
+ if (kind == 1 || kind == 2) {
+ s->range_table = &range_digital;
+ } else if (range) {
+ range_table_list = kmalloc_array(s->n_chan, sizeof(*range),
+ GFP_KERNEL);
+ if (!range_table_list)
+ return -ENOMEM;
+ s->range_table_list = range_table_list;
+ }
+ for (chan = 0, j = 0; j < 32; j++) {
+ if (cfg[j].kind == kind) {
+ if (mapping)
+ mapping[chan] = j;
+ if (range) {
+ range[j].length = 1;
+ range[j].range.min = cfg[j].min;
+ range[j].range.max = cfg[j].max;
+ range_table_list[chan] =
+ (const struct comedi_lrange *)&range[j];
+ }
+ maxdata_list[chan] = ((long long)1 << cfg[j].bits) - 1;
+ chan++;
+ }
+ }
+ return 0;
+}
+
+static int serial2002_setup_subdevs(struct comedi_device *dev)
+{
+ struct serial2002_private *devpriv = dev->private;
+ struct config_t *di_cfg;
+ struct config_t *do_cfg;
+ struct config_t *ai_cfg;
+ struct config_t *ao_cfg;
+ struct config_t *cfg;
+ struct comedi_subdevice *s;
+ int result = 0;
+ int i;
+
+ /* Allocate the temporary structs to hold the configuration data */
+ di_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ do_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ ai_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ ao_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ if (!di_cfg || !do_cfg || !ai_cfg || !ao_cfg) {
+ result = -ENOMEM;
+ goto err_alloc_configs;
+ }
+
+ /* Read the configuration from the connected device */
+ serial2002_tty_setspeed(devpriv->tty, devpriv->speed);
+ serial2002_poll_channel(devpriv->tty, 31);
+ while (1) {
+ struct serial_data data = serial2002_read(devpriv->tty, 1000);
+ int kind = S2002_CFG_KIND(data.value);
+ int channel = S2002_CFG_CHAN(data.value);
+ int range = S2002_CFG_BASE(data.value);
+ int cmd = S2002_CFG_CMD(data.value);
+
+ if (data.kind != is_channel || data.index != 31 ||
+ kind == S2002_CFG_KIND_INVALID)
+ break;
+
+ switch (kind) {
+ case S2002_CFG_KIND_DIGITAL_IN:
+ cfg = di_cfg;
+ break;
+ case S2002_CFG_KIND_DIGITAL_OUT:
+ cfg = do_cfg;
+ break;
+ case S2002_CFG_KIND_ANALOG_IN:
+ cfg = ai_cfg;
+ break;
+ case S2002_CFG_KIND_ANALOG_OUT:
+ cfg = ao_cfg;
+ break;
+ case S2002_CFG_KIND_ENCODER_IN:
+ cfg = ai_cfg;
+ break;
+ default:
+ cfg = NULL;
+ break;
+ }
+ if (!cfg)
+ continue; /* unknown kind, skip it */
+
+ cfg[channel].kind = kind;
+
+ switch (cmd) {
+ case S2002_CFG_CMD_BITS:
+ cfg[channel].bits = S2002_CFG_BITS(data.value);
+ break;
+ case S2002_CFG_CMD_MIN:
+ case S2002_CFG_CMD_MAX:
+ switch (S2002_CFG_UNITS(data.value)) {
+ case 0:
+ range *= 1000000;
+ break;
+ case 1:
+ range *= 1000;
+ break;
+ case 2:
+ range *= 1;
+ break;
+ }
+ if (S2002_CFG_SIGN(data.value))
+ range = -range;
+ if (cmd == S2002_CFG_CMD_MIN)
+ cfg[channel].min = range;
+ else
+ cfg[channel].max = range;
+ break;
+ }
+ }
+
+ /* Fill in subdevice data */
+ for (i = 0; i <= 4; i++) {
+ unsigned char *mapping = NULL;
+ struct serial2002_range_table_t *range = NULL;
+ int kind = 0;
+
+ s = &dev->subdevices[i];
+
+ switch (i) {
+ case 0:
+ cfg = di_cfg;
+ mapping = devpriv->digital_in_mapping;
+ kind = S2002_CFG_KIND_DIGITAL_IN;
+ break;
+ case 1:
+ cfg = do_cfg;
+ mapping = devpriv->digital_out_mapping;
+ kind = S2002_CFG_KIND_DIGITAL_OUT;
+ break;
+ case 2:
+ cfg = ai_cfg;
+ mapping = devpriv->analog_in_mapping;
+ range = devpriv->in_range;
+ kind = S2002_CFG_KIND_ANALOG_IN;
+ break;
+ case 3:
+ cfg = ao_cfg;
+ mapping = devpriv->analog_out_mapping;
+ range = devpriv->out_range;
+ kind = S2002_CFG_KIND_ANALOG_OUT;
+ break;
+ case 4:
+ cfg = ai_cfg;
+ mapping = devpriv->encoder_in_mapping;
+ range = devpriv->in_range;
+ kind = S2002_CFG_KIND_ENCODER_IN;
+ break;
+ }
+
+ if (serial2002_setup_subdevice(s, cfg, range, mapping, kind))
+ break; /* err handled below */
+ }
+ if (i <= 4) {
+ /*
+ * Failed to allocate maxdata_list or range_table_list
+ * for a subdevice that needed it.
+ */
+ result = -ENOMEM;
+ for (i = 0; i <= 4; i++) {
+ s = &dev->subdevices[i];
+ kfree(s->maxdata_list);
+ s->maxdata_list = NULL;
+ kfree(s->range_table_list);
+ s->range_table_list = NULL;
+ }
+ }
+
+err_alloc_configs:
+ kfree(di_cfg);
+ kfree(do_cfg);
+ kfree(ai_cfg);
+ kfree(ao_cfg);
+
+ if (result) {
+ if (devpriv->tty) {
+ filp_close(devpriv->tty, NULL);
+ devpriv->tty = NULL;
+ }
+ }
+
+ return result;
+}
+
+static int serial2002_open(struct comedi_device *dev)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int result;
+ char port[20];
+
+ sprintf(port, "/dev/ttyS%d", devpriv->port);
+ devpriv->tty = filp_open(port, O_RDWR, 0);
+ if (IS_ERR(devpriv->tty)) {
+ result = (int)PTR_ERR(devpriv->tty);
+ dev_err(dev->class_dev, "file open error = %d\n", result);
+ } else {
+ result = serial2002_setup_subdevs(dev);
+ }
+ return result;
+}
+
+static void serial2002_close(struct comedi_device *dev)
+{
+ struct serial2002_private *devpriv = dev->private;
+
+ if (!IS_ERR(devpriv->tty) && devpriv->tty)
+ filp_close(devpriv->tty, NULL);
+}
+
+static int serial2002_di_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data read;
+
+ serial2002_poll_digital(devpriv->tty, chan);
+ while (1) {
+ read = serial2002_read(devpriv->tty, 1000);
+ if (read.kind != is_digital || read.index == chan)
+ break;
+ }
+ data[n] = read.value;
+ }
+ return n;
+}
+
+static int serial2002_do_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data write;
+
+ write.kind = is_digital;
+ write.index = chan;
+ write.value = data[n];
+ serial2002_write(devpriv->tty, write);
+ }
+ return n;
+}
+
+static int serial2002_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data read;
+
+ serial2002_poll_channel(devpriv->tty, chan);
+ while (1) {
+ read = serial2002_read(devpriv->tty, 1000);
+ if (read.kind != is_channel || read.index == chan)
+ break;
+ }
+ data[n] = read.value;
+ }
+ return n;
+}
+
+static int serial2002_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data write;
+
+ write.kind = is_channel;
+ write.index = chan;
+ write.value = data[n];
+ serial2002_write(devpriv->tty, write);
+ devpriv->ao_readback[chan] = data[n];
+ }
+ return n;
+}
+
+static int serial2002_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan = CR_CHAN(insn->chanspec);
+
+ for (n = 0; n < insn->n; n++)
+ data[n] = devpriv->ao_readback[chan];
+
+ return n;
+}
+
+static int serial2002_encoder_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data read;
+
+ serial2002_poll_channel(devpriv->tty, chan);
+ while (1) {
+ read = serial2002_read(devpriv->tty, 1000);
+ if (read.kind != is_channel || read.index == chan)
+ break;
+ }
+ data[n] = read.value;
+ }
+ return n;
+}
+
+static int serial2002_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct serial2002_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->port = it->options[0];
+ devpriv->speed = it->options[1];
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* digital input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 0;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_read = serial2002_di_insn_read;
+
+ /* digital output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 0;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_write = serial2002_do_insn_write;
+
+ /* analog input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 0;
+ s->maxdata = 1;
+ s->range_table = NULL;
+ s->insn_read = serial2002_ai_insn_read;
+
+ /* analog output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 0;
+ s->maxdata = 1;
+ s->range_table = NULL;
+ s->insn_write = serial2002_ao_insn_write;
+ s->insn_read = serial2002_ao_insn_read;
+
+ /* encoder input subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = 0;
+ s->maxdata = 1;
+ s->range_table = NULL;
+ s->insn_read = serial2002_encoder_insn_read;
+
+ dev->open = serial2002_open;
+ dev->close = serial2002_close;
+
+ return 0;
+}
+
+static void serial2002_detach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ kfree(s->maxdata_list);
+ kfree(s->range_table_list);
+ }
+}
+
+static struct comedi_driver serial2002_driver = {
+ .driver_name = "serial2002",
+ .module = THIS_MODULE,
+ .attach = serial2002_attach,
+ .detach = serial2002_detach,
+};
+module_comedi_driver(serial2002_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ssv_dnp.c b/drivers/staging/comedi/drivers/ssv_dnp.c
new file mode 100644
index 000000000..acc7f3445
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ssv_dnp.c
@@ -0,0 +1,186 @@
+/*
+ comedi/drivers/ssv_dnp.c
+ generic comedi driver for SSV Embedded Systems' DIL/Net-PCs
+ Copyright (C) 2001 Robert Schwebel <robert@schwebel.de>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+Driver: ssv_dnp
+Description: SSV Embedded Systems DIL/Net-PC
+Author: Robert Schwebel <robert@schwebel.de>
+Devices: [SSV Embedded Systems] DIL/Net-PC 1486 (dnp-1486)
+Status: unknown
+*/
+
+/* include files ----------------------------------------------------------- */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/* Some global definitions: the registers of the DNP ----------------------- */
+/* */
+/* For port A and B the mode register has bits corresponding to the output */
+/* pins, where Bit-N = 0 -> input, Bit-N = 1 -> output. Note that bits */
+/* 4 to 7 correspond to pin 0..3 for port C data register. Ensure that bits */
+/* 0..3 remain unchanged! For details about Port C Mode Register see */
+/* the remarks in dnp_insn_config() below. */
+
+#define CSCIR 0x22 /* Chip Setup and Control Index Register */
+#define CSCDR 0x23 /* Chip Setup and Control Data Register */
+#define PAMR 0xa5 /* Port A Mode Register */
+#define PADR 0xa9 /* Port A Data Register */
+#define PBMR 0xa4 /* Port B Mode Register */
+#define PBDR 0xa8 /* Port B Data Register */
+#define PCMR 0xa3 /* Port C Mode Register */
+#define PCDR 0xa7 /* Port C Data Register */
+
+static int dnp_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int val;
+
+ /*
+ * Ports A and B are straight forward: each bit corresponds to an
+ * output pin with the same order. Port C is different: bits 0...3
+ * correspond to bits 4...7 of the output register (PCDR).
+ */
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ outb(PADR, CSCIR);
+ outb(s->state & 0xff, CSCDR);
+
+ outb(PBDR, CSCIR);
+ outb((s->state >> 8) & 0xff, CSCDR);
+
+ outb(PCDR, CSCIR);
+ val = inb(CSCDR) & 0x0f;
+ outb(((s->state >> 12) & 0xf0) | val, CSCDR);
+ }
+
+ outb(PADR, CSCIR);
+ val = inb(CSCDR);
+ outb(PBDR, CSCIR);
+ val |= (inb(CSCDR) << 8);
+ outb(PCDR, CSCIR);
+ val |= ((inb(CSCDR) & 0xf0) << 12);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dnp_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ unsigned int val;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ if (chan < 8) { /* Port A */
+ mask = 1 << chan;
+ outb(PAMR, CSCIR);
+ } else if (chan < 16) { /* Port B */
+ mask = 1 << (chan - 8);
+ outb(PBMR, CSCIR);
+ } else { /* Port C */
+ /*
+ * We have to pay attention with port C.
+ * This is the meaning of PCMR:
+ * Bit in PCMR: 7 6 5 4 3 2 1 0
+ * Corresponding port C pin: d 3 d 2 d 1 d 0 d= don't touch
+ *
+ * Multiplication by 2 brings bits into correct position
+ * for PCMR!
+ */
+ mask = 1 << ((chan - 16) * 2);
+ outb(PCMR, CSCIR);
+ }
+
+ val = inb(CSCDR);
+ if (data[0] == COMEDI_OUTPUT)
+ val |= mask;
+ else
+ val &= ~mask;
+ outb(val, CSCDR);
+
+ return insn->n;
+}
+
+static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* digital i/o subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 20;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dnp_dio_insn_bits;
+ s->insn_config = dnp_dio_insn_config;
+
+ /* We use the I/O ports 0x22,0x23 and 0xa3-0xa9, which are always
+ * allocated for the primary 8259, so we don't need to allocate them
+ * ourselves. */
+
+ /* configure all ports as input (default) */
+ outb(PAMR, CSCIR);
+ outb(0x00, CSCDR);
+ outb(PBMR, CSCIR);
+ outb(0x00, CSCDR);
+ outb(PCMR, CSCIR);
+ outb((inb(CSCDR) & 0xAA), CSCDR);
+
+ return 0;
+}
+
+static void dnp_detach(struct comedi_device *dev)
+{
+ outb(PAMR, CSCIR);
+ outb(0x00, CSCDR);
+ outb(PBMR, CSCIR);
+ outb(0x00, CSCDR);
+ outb(PCMR, CSCIR);
+ outb((inb(CSCDR) & 0xAA), CSCDR);
+}
+
+static struct comedi_driver dnp_driver = {
+ .driver_name = "dnp-1486",
+ .module = THIS_MODULE,
+ .attach = dnp_attach,
+ .detach = dnp_detach,
+};
+module_comedi_driver(dnp_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/unioxx5.c b/drivers/staging/comedi/drivers/unioxx5.c
new file mode 100644
index 000000000..51498b889
--- /dev/null
+++ b/drivers/staging/comedi/drivers/unioxx5.c
@@ -0,0 +1,506 @@
+/***************************************************************************
+ * *
+ * comedi/drivers/unioxx5.c *
+ * Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards. *
+ * *
+ * Copyright (C) 2006 Kruchinin Daniil (asgard) [asgard@etersoft.ru] *
+ * *
+ * COMEDI - Linux Control and Measurement Device Interface *
+ * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ ***************************************************************************/
+/*
+
+Driver: unioxx5
+Description: Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards.
+Author: Kruchinin Daniil (asgard) <asgard@etersoft.ru>
+Status: unknown
+Updated: 2006-10-09
+Devices: [Fastwel] UNIOxx-5 (unioxx5),
+
+ This card supports digital and analog I/O. It written for g01
+ subdevices only.
+ channels range: 0 .. 23 dio channels
+ and 0 .. 11 analog modules range
+ During attaching unioxx5 module displays modules identifiers
+ (see dmesg after comedi_config) in format:
+ | [module_number] module_id |
+
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include "../comedidev.h"
+
+#define UNIOXX5_SIZE 0x10
+#define UNIOXX5_SUBDEV_BASE 0xA000 /* base addr of first subdev */
+#define UNIOXX5_SUBDEV_ODDS 0x400
+
+/* modules types */
+#define MODULE_DIGITAL 0
+#define MODULE_OUTPUT_MASK 0x80 /* analog input/output */
+
+/* constants for digital i/o */
+#define UNIOXX5_NUM_OF_CHANS 24
+
+/* constants for analog i/o */
+#define TxBE 0x10 /* transmit buffer enable */
+#define RxCA 0x20 /* 1 receive character available */
+#define Rx2CA 0x40 /* 2 receive character available */
+#define Rx4CA 0x80 /* 4 receive character available */
+
+/* bytes mask errors */
+#define Rx2CA_ERR_MASK 0x04 /* 2 bytes receiving error */
+#define Rx4CA_ERR_MASK 0x08 /* 4 bytes receiving error */
+
+/* channel modes */
+#define ALL_2_INPUT 0 /* config all digital channels to input */
+#define ALL_2_OUTPUT 1 /* config all digital channels to output */
+
+/* 'private' structure for each subdevice */
+struct unioxx5_subd_priv {
+ int usp_iobase;
+ /* 12 modules. each can be 70L or 73L */
+ unsigned char usp_module_type[12];
+ /* for saving previous written value for analog modules */
+ unsigned char usp_extra_data[12][4];
+ unsigned char usp_prev_wr_val[3]; /* previous written value */
+ unsigned char usp_prev_cn_val[3]; /* previous channel value */
+};
+
+static int __unioxx5_define_chan_offset(int chan_num)
+{
+ if (chan_num < 0 || chan_num > 23)
+ return -1;
+
+ return (chan_num >> 3) + 1;
+}
+
+#if 0 /* not used? */
+static void __unioxx5_digital_config(struct comedi_subdevice *s, int mode)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int i, mask;
+
+ mask = (mode == ALL_2_OUTPUT) ? 0xFF : 0x00;
+ dev_dbg(csdev, "mode = %d\n", mask);
+
+ outb(1, usp->usp_iobase + 0);
+
+ for (i = 0; i < 3; i++)
+ outb(mask, usp->usp_iobase + i);
+
+ outb(0, usp->usp_iobase + 0);
+}
+#endif
+
+/* configure channels for analog i/o (even to output, odd to input) */
+static void __unioxx5_analog_config(struct unioxx5_subd_priv *usp, int channel)
+{
+ int chan_a, chan_b, conf, channel_offset;
+
+ channel_offset = __unioxx5_define_chan_offset(channel);
+ conf = usp->usp_prev_cn_val[channel_offset - 1];
+ chan_a = chan_b = 1;
+
+ /* setting channel A and channel B mask */
+ if (channel % 2 == 0) {
+ chan_a <<= channel & 0x07;
+ chan_b <<= (channel + 1) & 0x07;
+ } else {
+ chan_a <<= (channel - 1) & 0x07;
+ chan_b <<= channel & 0x07;
+ }
+
+ conf |= chan_a; /* even channel ot output */
+ conf &= ~chan_b; /* odd channel to input */
+
+ outb(1, usp->usp_iobase + 0);
+ outb(conf, usp->usp_iobase + channel_offset);
+ outb(0, usp->usp_iobase + 0);
+
+ usp->usp_prev_cn_val[channel_offset - 1] = conf;
+}
+
+static int __unioxx5_digital_read(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int channel_offset, mask = 1 << (channel & 0x07);
+
+ channel_offset = __unioxx5_define_chan_offset(channel);
+ if (channel_offset < 0) {
+ dev_err(csdev,
+ "undefined channel %d. channel range is 0 .. 23\n",
+ channel);
+ return 0;
+ }
+
+ *data = inb(usp->usp_iobase + channel_offset);
+ *data &= mask;
+
+ /* correct the read value to 0 or 1 */
+ if (channel_offset > 1)
+ channel -= 2 << channel_offset;
+ *data >>= channel;
+ return 1;
+}
+
+static int __unioxx5_analog_read(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int module_no, read_ch;
+ char control;
+
+ module_no = channel / 2;
+ read_ch = channel % 2; /* depend on type of channel (A or B) */
+
+ /* defining if given module can work on input */
+ if (usp->usp_module_type[module_no] & MODULE_OUTPUT_MASK) {
+ dev_err(csdev,
+ "module in position %d with id 0x%02x is for output only",
+ module_no, usp->usp_module_type[module_no]);
+ return 0;
+ }
+
+ __unioxx5_analog_config(usp, channel);
+ /* sends module number to card(1 .. 12) */
+ outb(module_no + 1, usp->usp_iobase + 5);
+ outb('V', usp->usp_iobase + 6); /* sends to module (V)erify command */
+ control = inb(usp->usp_iobase); /* get control register byte */
+
+ /* waits while reading four bytes will be allowed */
+ while (!((control = inb(usp->usp_iobase + 0)) & Rx4CA))
+ ;
+
+ /* if four bytes readding error occurs - return 0(false) */
+ if ((control & Rx4CA_ERR_MASK)) {
+ dev_err(csdev, "4 bytes error\n");
+ return 0;
+ }
+
+ if (read_ch)
+ *data = inw(usp->usp_iobase + 6); /* channel B */
+ else
+ *data = inw(usp->usp_iobase + 4); /* channel A */
+
+ return 1;
+}
+
+static int __unioxx5_digital_write(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int channel_offset, val;
+ int mask = 1 << (channel & 0x07);
+
+ channel_offset = __unioxx5_define_chan_offset(channel);
+ if (channel_offset < 0) {
+ dev_err(csdev,
+ "undefined channel %d. channel range is 0 .. 23\n",
+ channel);
+ return 0;
+ }
+
+ /* getting previous written value */
+ val = usp->usp_prev_wr_val[channel_offset - 1];
+
+ if (*data)
+ val |= mask;
+ else
+ val &= ~mask;
+
+ outb(val, usp->usp_iobase + channel_offset);
+ /* saving new written value */
+ usp->usp_prev_wr_val[channel_offset - 1] = val;
+
+ return 1;
+}
+
+static int __unioxx5_analog_write(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int module, i;
+
+ module = channel / 2; /* definig module number(0 .. 11) */
+ i = (channel % 2) << 1; /* depends on type of channel (A or B) */
+
+ /* defining if given module can work on output */
+ if (!(usp->usp_module_type[module] & MODULE_OUTPUT_MASK)) {
+ dev_err(csdev,
+ "module in position %d with id 0x%0x is for input only!\n",
+ module, usp->usp_module_type[module]);
+ return 0;
+ }
+
+ __unioxx5_analog_config(usp, channel);
+ /* saving minor byte */
+ usp->usp_extra_data[module][i++] = (unsigned char)(*data & 0x00FF);
+ /* saving major byte */
+ usp->usp_extra_data[module][i] = (unsigned char)((*data & 0xFF00) >> 8);
+
+ /* while(!((inb(usp->usp_iobase + 0)) & TxBE)); */
+ /* sending module number to card(1 .. 12) */
+ outb(module + 1, usp->usp_iobase + 5);
+ outb('W', usp->usp_iobase + 6); /* sends (W)rite command to module */
+
+ /* sending for bytes to module(one byte per cycle iteration) */
+ for (i = 0; i < 4; i++) {
+ while (!((inb(usp->usp_iobase + 0)) & TxBE))
+ ; /* waits while writing will be allowed */
+ outb(usp->usp_extra_data[module][i], usp->usp_iobase + 6);
+ }
+
+ return 1;
+}
+
+static int unioxx5_subdev_read(struct comedi_device *dev,
+ struct comedi_subdevice *subdev,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct unioxx5_subd_priv *usp = subdev->private;
+ int channel, type;
+
+ channel = CR_CHAN(insn->chanspec);
+ /* defining module type(analog or digital) */
+ type = usp->usp_module_type[channel / 2];
+
+ if (type == MODULE_DIGITAL) {
+ if (!__unioxx5_digital_read(subdev, data, channel, dev->minor))
+ return -1;
+ } else {
+ if (!__unioxx5_analog_read(subdev, data, channel, dev->minor))
+ return -1;
+ }
+
+ return 1;
+}
+
+static int unioxx5_subdev_write(struct comedi_device *dev,
+ struct comedi_subdevice *subdev,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct unioxx5_subd_priv *usp = subdev->private;
+ int channel, type;
+
+ channel = CR_CHAN(insn->chanspec);
+ /* defining module type(analog or digital) */
+ type = usp->usp_module_type[channel / 2];
+
+ if (type == MODULE_DIGITAL) {
+ if (!__unioxx5_digital_write(subdev, data, channel, dev->minor))
+ return -1;
+ } else {
+ if (!__unioxx5_analog_write(subdev, data, channel, dev->minor))
+ return -1;
+ }
+
+ return 1;
+}
+
+/* for digital modules only */
+static int unioxx5_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *subdev,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int channel_offset, flags, channel = CR_CHAN(insn->chanspec), type;
+ struct unioxx5_subd_priv *usp = subdev->private;
+ int mask = 1 << (channel & 0x07);
+
+ type = usp->usp_module_type[channel / 2];
+
+ if (type != MODULE_DIGITAL) {
+ dev_err(dev->class_dev,
+ "channel configuration accessible only for digital modules\n");
+ return -1;
+ }
+
+ channel_offset = __unioxx5_define_chan_offset(channel);
+ if (channel_offset < 0) {
+ dev_err(dev->class_dev,
+ "undefined channel %d. channel range is 0 .. 23\n",
+ channel);
+ return -1;
+ }
+
+ /* gets previously written value */
+ flags = usp->usp_prev_cn_val[channel_offset - 1];
+
+ switch (*data) {
+ case COMEDI_INPUT:
+ flags &= ~mask;
+ break;
+ case COMEDI_OUTPUT:
+ flags |= mask;
+ break;
+ default:
+ dev_err(dev->class_dev, "unknown flag\n");
+ return -1;
+ }
+
+ /* *\
+ * sets channels buffer to 1(after this we are allowed to *
+ * change channel type on input or output) *
+ \* */
+ outb(1, usp->usp_iobase + 0);
+ /* changes type of _one_ channel */
+ outb(flags, usp->usp_iobase + channel_offset);
+ /* sets channels bank to 0(allows directly input/output) */
+ outb(0, usp->usp_iobase + 0);
+ /* saves written value */
+ usp->usp_prev_cn_val[channel_offset - 1] = flags;
+
+ return 0;
+}
+
+/* initializing subdevice with given address */
+static int __unioxx5_subdev_init(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ int iobase)
+{
+ struct unioxx5_subd_priv *usp;
+ int i, to, ndef_flag = 0;
+ int ret;
+
+ usp = comedi_alloc_spriv(s, sizeof(*usp));
+ if (!usp)
+ return -ENOMEM;
+
+ ret = __comedi_request_region(dev, iobase, UNIOXX5_SIZE);
+ if (ret)
+ return ret;
+ usp->usp_iobase = iobase;
+
+ /* defining modules types */
+ for (i = 0; i < 12; i++) {
+ to = 10000;
+
+ __unioxx5_analog_config(usp, i * 2);
+ /* sends channel number to card */
+ outb(i + 1, iobase + 5);
+ outb('H', iobase + 6); /* requests EEPROM world */
+ while (!(inb(iobase + 0) & TxBE))
+ ; /* waits while writing will be allowed */
+ outb(0, iobase + 6);
+
+ /* waits while reading of two bytes will be allowed */
+ while (!(inb(iobase + 0) & Rx2CA)) {
+ if (--to <= 0) {
+ ndef_flag = 1;
+ break;
+ }
+ }
+
+ if (ndef_flag) {
+ usp->usp_module_type[i] = 0;
+ ndef_flag = 0;
+ } else {
+ usp->usp_module_type[i] = inb(iobase + 6);
+ }
+
+ udelay(1);
+ }
+
+ /* initial subdevice for digital or analog i/o */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = UNIOXX5_NUM_OF_CHANS;
+ s->maxdata = 0xFFF;
+ s->range_table = &range_digital;
+ s->insn_read = unioxx5_subdev_read;
+ s->insn_write = unioxx5_subdev_write;
+ /* for digital modules only!!! */
+ s->insn_config = unioxx5_insn_config;
+
+ return 0;
+}
+
+static int unioxx5_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int iobase, i, n_subd;
+ int id, num, ba;
+ int ret;
+
+ iobase = it->options[0];
+
+ dev->iobase = iobase;
+ iobase += UNIOXX5_SUBDEV_BASE;
+ n_subd = 0;
+
+ /* getting number of subdevices with types 'g01' */
+ for (i = 0, ba = iobase; i < 4; i++, ba += UNIOXX5_SUBDEV_ODDS) {
+ id = inb(ba + 0xE);
+ num = inb(ba + 0xF);
+
+ if (id != 'g' || num != 1)
+ continue;
+
+ n_subd++;
+ }
+
+ /* unioxx5 can has from two to four subdevices */
+ if (n_subd < 2) {
+ dev_err(dev->class_dev,
+ "your card must has at least 2 'g01' subdevices\n");
+ return -1;
+ }
+
+ ret = comedi_alloc_subdevices(dev, n_subd);
+ if (ret)
+ return ret;
+
+ /* initializing each of for same subdevices */
+ for (i = 0; i < n_subd; i++, iobase += UNIOXX5_SUBDEV_ODDS) {
+ s = &dev->subdevices[i];
+ ret = __unioxx5_subdev_init(dev, s, iobase);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void unioxx5_detach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ struct unioxx5_subd_priv *spriv;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+ if (spriv && spriv->usp_iobase)
+ release_region(spriv->usp_iobase, UNIOXX5_SIZE);
+ }
+}
+
+static struct comedi_driver unioxx5_driver = {
+ .driver_name = "unioxx5",
+ .module = THIS_MODULE,
+ .attach = unioxx5_attach,
+ .detach = unioxx5_detach,
+};
+module_comedi_driver(unioxx5_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/usbdux.c b/drivers/staging/comedi/drivers/usbdux.c
new file mode 100644
index 000000000..ced05e581
--- /dev/null
+++ b/drivers/staging/comedi/drivers/usbdux.c
@@ -0,0 +1,1751 @@
+/*
+ * usbdux.c
+ * Copyright (C) 2003-2014 Bernd Porr, mail@berndporr.me.uk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: usbdux
+ * Description: University of Stirling USB DAQ & INCITE Technology Limited
+ * Devices: [ITL] USB-DUX (usbdux)
+ * Author: Bernd Porr <mail@berndporr.me.uk>
+ * Updated: 10 Oct 2014
+ * Status: Stable
+ *
+ * Connection scheme for the counter at the digital port:
+ * 0=/CLK0, 1=UP/DOWN0, 2=RESET0, 4=/CLK1, 5=UP/DOWN1, 6=RESET1.
+ * The sampling rate of the counter is approximately 500Hz.
+ *
+ * Note that under USB2.0 the length of the channel list determines
+ * the max sampling rate. If you sample only one channel you get 8kHz
+ * sampling rate. If you sample two channels you get 4kHz and so on.
+ */
+
+/*
+ * I must give credit here to Chris Baugher who
+ * wrote the driver for AT-MIO-16d. I used some parts of this
+ * driver. I also must give credits to David Brownell
+ * who supported me with the USB development.
+ *
+ * Bernd Porr
+ *
+ *
+ * Revision history:
+ * 0.94: D/A output should work now with any channel list combinations
+ * 0.95: .owner commented out for kernel vers below 2.4.19
+ * sanity checks in ai/ao_cmd
+ * 0.96: trying to get it working with 2.6, moved all memory alloc to comedi's
+ * attach final USB IDs
+ * moved memory allocation completely to the corresponding comedi
+ * functions firmware upload is by fxload and no longer by comedi (due to
+ * enumeration)
+ * 0.97: USB IDs received, adjusted table
+ * 0.98: SMP, locking, memory alloc: moved all usb memory alloc
+ * to the usb subsystem and moved all comedi related memory
+ * alloc to comedi.
+ * | kernel | registration | usbdux-usb | usbdux-comedi | comedi |
+ * 0.99: USB 2.0: changed protocol to isochronous transfer
+ * IRQ transfer is too buggy and too risky in 2.0
+ * for the high speed ISO transfer is now a working version
+ * available
+ * 0.99b: Increased the iso transfer buffer for high sp.to 10 buffers. Some VIA
+ * chipsets miss out IRQs. Deeper buffering is needed.
+ * 1.00: full USB 2.0 support for the A/D converter. Now: max 8kHz sampling
+ * rate.
+ * Firmware vers 1.00 is needed for this.
+ * Two 16 bit up/down/reset counter with a sampling rate of 1kHz
+ * And loads of cleaning up, in particular streamlining the
+ * bulk transfers.
+ * 1.1: moved EP4 transfers to EP1 to make space for a PWM output on EP4
+ * 1.2: added PWM support via EP4
+ * 2.0: PWM seems to be stable and is not interfering with the other functions
+ * 2.1: changed PWM API
+ * 2.2: added firmware kernel request to fix an udev problem
+ * 2.3: corrected a bug in bulk timeouts which were far too short
+ * 2.4: fixed a bug which causes the driver to hang when it ran out of data.
+ * Thanks to Jan-Matthias Braun and Ian to spot the bug and fix it.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/fcntl.h>
+#include <linux/compiler.h>
+
+#include "../comedi_usb.h"
+
+/* constants for firmware upload and download */
+#define USBDUX_FIRMWARE "usbdux_firmware.bin"
+#define USBDUX_FIRMWARE_MAX_LEN 0x2000
+#define USBDUX_FIRMWARE_CMD 0xa0
+#define VENDOR_DIR_IN 0xc0
+#define VENDOR_DIR_OUT 0x40
+#define USBDUX_CPU_CS 0xe600
+
+/* usbdux bulk transfer commands */
+#define USBDUX_CMD_MULT_AI 0
+#define USBDUX_CMD_AO 1
+#define USBDUX_CMD_DIO_CFG 2
+#define USBDUX_CMD_DIO_BITS 3
+#define USBDUX_CMD_SINGLE_AI 4
+#define USBDUX_CMD_TIMER_RD 5
+#define USBDUX_CMD_TIMER_WR 6
+#define USBDUX_CMD_PWM_ON 7
+#define USBDUX_CMD_PWM_OFF 8
+
+/* timeout for the USB-transfer in ms */
+#define BULK_TIMEOUT 1000
+
+/* 300Hz max frequ under PWM */
+#define MIN_PWM_PERIOD ((long)(1E9/300))
+
+/* Default PWM frequency */
+#define PWM_DEFAULT_PERIOD ((long)(1E9/100))
+
+/* Size of one A/D value */
+#define SIZEADIN ((sizeof(uint16_t)))
+
+/*
+ * Size of the input-buffer IN BYTES
+ * Always multiple of 8 for 8 microframes which is needed in the highspeed mode
+ */
+#define SIZEINBUF ((8*SIZEADIN))
+
+/* 16 bytes. */
+#define SIZEINSNBUF 16
+
+/* size of one value for the D/A converter: channel and value */
+#define SIZEDAOUT ((sizeof(uint8_t)+sizeof(uint16_t)))
+
+/*
+ * Size of the output-buffer in bytes
+ * Actually only the first 4 triplets are used but for the
+ * high speed mode we need to pad it to 8 (microframes).
+ */
+#define SIZEOUTBUF ((8*SIZEDAOUT))
+
+/*
+ * Size of the buffer for the dux commands: just now max size is determined
+ * by the analogue out + command byte + panic bytes...
+ */
+#define SIZEOFDUXBUFFER ((8*SIZEDAOUT+2))
+
+/* Number of in-URBs which receive the data: min=2 */
+#define NUMOFINBUFFERSFULL 5
+
+/* Number of out-URBs which send the data: min=2 */
+#define NUMOFOUTBUFFERSFULL 5
+
+/* Number of in-URBs which receive the data: min=5 */
+/* must have more buffers due to buggy USB ctr */
+#define NUMOFINBUFFERSHIGH 10
+
+/* Number of out-URBs which send the data: min=5 */
+/* must have more buffers due to buggy USB ctr */
+#define NUMOFOUTBUFFERSHIGH 10
+
+/* number of retries to get the right dux command */
+#define RETRIES 10
+
+static const struct comedi_lrange range_usbdux_ai_range = {
+ 4, {
+ BIP_RANGE(4.096),
+ BIP_RANGE(4.096 / 2),
+ UNI_RANGE(4.096),
+ UNI_RANGE(4.096 / 2)
+ }
+};
+
+static const struct comedi_lrange range_usbdux_ao_range = {
+ 2, {
+ BIP_RANGE(4.096),
+ UNI_RANGE(4.096)
+ }
+};
+
+struct usbdux_private {
+ /* actual number of in-buffers */
+ int n_ai_urbs;
+ /* actual number of out-buffers */
+ int n_ao_urbs;
+ /* ISO-transfer handling: buffers */
+ struct urb **ai_urbs;
+ struct urb **ao_urbs;
+ /* pwm-transfer handling */
+ struct urb *pwm_urb;
+ /* PWM period */
+ unsigned int pwm_period;
+ /* PWM internal delay for the GPIF in the FX2 */
+ uint8_t pwm_delay;
+ /* size of the PWM buffer which holds the bit pattern */
+ int pwm_buf_sz;
+ /* input buffer for the ISO-transfer */
+ __le16 *in_buf;
+ /* input buffer for single insn */
+ __le16 *insn_buf;
+
+ unsigned int high_speed:1;
+ unsigned int ai_cmd_running:1;
+ unsigned int ao_cmd_running:1;
+ unsigned int pwm_cmd_running:1;
+
+ /* time between samples in units of the timer */
+ unsigned int ai_timer;
+ unsigned int ao_timer;
+ /* counter between aquisitions */
+ unsigned int ai_counter;
+ unsigned int ao_counter;
+ /* interval in frames/uframes */
+ unsigned int ai_interval;
+ /* commands */
+ uint8_t *dux_commands;
+ struct semaphore sem;
+};
+
+static void usbdux_unlink_urbs(struct urb **urbs, int num_urbs)
+{
+ int i;
+
+ for (i = 0; i < num_urbs; i++)
+ usb_kill_urb(urbs[i]);
+}
+
+static void usbdux_ai_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ai_urbs)
+ usbdux_unlink_urbs(devpriv->ai_urbs, devpriv->n_ai_urbs);
+
+ devpriv->ai_cmd_running = 0;
+}
+
+static int usbdux_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ /* prevent other CPUs from submitting new commands just now */
+ down(&devpriv->sem);
+ /* unlink only if the urb really has been submitted */
+ usbdux_ai_stop(dev, devpriv->ai_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static void usbduxsub_ai_handle_urb(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct urb *urb)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int ret;
+ int i;
+
+ devpriv->ai_counter--;
+ if (devpriv->ai_counter == 0) {
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ /* get the data from the USB bus and hand it over to comedi */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ uint16_t val = le16_to_cpu(devpriv->in_buf[i]);
+
+ /* bipolar data is two's-complement */
+ if (comedi_range_is_bipolar(s, range))
+ val ^= ((s->maxdata + 1) >> 1);
+
+ /* transfer data */
+ if (!comedi_buf_write_samples(s, &val, 1))
+ return;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ /* if command is still running, resubmit urb */
+ if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
+ urb->dev = comedi_to_usb_dev(dev);
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "urb resubmit failed in int-context! err=%d\n",
+ ret);
+ if (ret == -EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handler!\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+ }
+}
+
+static void usbduxsub_ai_isoc_irq(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct usbdux_private *devpriv = dev->private;
+
+ /* exit if not running a command, do not resubmit urb */
+ if (!devpriv->ai_cmd_running)
+ return;
+
+ switch (urb->status) {
+ case 0:
+ /* copy the result in the transfer buffer */
+ memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF);
+ usbduxsub_ai_handle_urb(dev, s, urb);
+ break;
+
+ case -EILSEQ:
+ /*
+ * error in the ISOchronous data
+ * we don't copy the data into the transfer buffer
+ * and recycle the last data byte
+ */
+ dev_dbg(dev->class_dev, "CRC error in ISO IN stream\n");
+ usbduxsub_ai_handle_urb(dev, s, urb);
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* after an unlink command, unplug, ... etc */
+ async->events |= COMEDI_CB_ERROR;
+ break;
+
+ default:
+ /* a real error */
+ dev_err(dev->class_dev,
+ "Non-zero urb status received in ai intr context: %d\n",
+ urb->status);
+ async->events |= COMEDI_CB_ERROR;
+ break;
+ }
+
+ /*
+ * comedi_handle_events() cannot be used in this driver. The (*cancel)
+ * operation would unlink the urb.
+ */
+ if (async->events & COMEDI_CB_CANCEL_MASK)
+ usbdux_ai_stop(dev, 0);
+
+ comedi_event(dev, s);
+}
+
+static void usbdux_ao_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ao_urbs)
+ usbdux_unlink_urbs(devpriv->ao_urbs, devpriv->n_ao_urbs);
+
+ devpriv->ao_cmd_running = 0;
+}
+
+static int usbdux_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ /* prevent other CPUs from submitting a command just now */
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbdux_ao_stop(dev, devpriv->ao_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static void usbduxsub_ao_handle_urb(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct urb *urb)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ uint8_t *datap;
+ int ret;
+ int i;
+
+ devpriv->ao_counter--;
+ if (devpriv->ao_counter == 0) {
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ return;
+ }
+
+ /* transmit data to the USB bus */
+ datap = urb->transfer_buffer;
+ *datap++ = cmd->chanlist_len;
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned short val;
+
+ if (!comedi_buf_read_samples(s, &val, 1)) {
+ dev_err(dev->class_dev, "buffer underflow\n");
+ async->events |= COMEDI_CB_OVERFLOW;
+ return;
+ }
+
+ /* pointer to the DA */
+ *datap++ = val & 0xff;
+ *datap++ = (val >> 8) & 0xff;
+ *datap++ = chan << 6;
+ s->readback[chan] = val;
+ }
+ }
+
+ /* if command is still running, resubmit urb for BULK transfer */
+ if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ urb->number_of_packets = 1;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEOUTBUF;
+ urb->iso_frame_desc[0].status = 0;
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "ao urb resubm failed in int-cont. ret=%d",
+ ret);
+ if (ret == -EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handling!\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+ }
+}
+
+static void usbduxsub_ao_isoc_irq(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct comedi_subdevice *s = dev->write_subdev;
+ struct comedi_async *async = s->async;
+ struct usbdux_private *devpriv = dev->private;
+
+ /* exit if not running a command, do not resubmit urb */
+ if (!devpriv->ao_cmd_running)
+ return;
+
+ switch (urb->status) {
+ case 0:
+ usbduxsub_ao_handle_urb(dev, s, urb);
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* after an unlink command, unplug, ... etc */
+ async->events |= COMEDI_CB_ERROR;
+ break;
+
+ default:
+ /* a real error */
+ dev_err(dev->class_dev,
+ "Non-zero urb status received in ao intr context: %d\n",
+ urb->status);
+ async->events |= COMEDI_CB_ERROR;
+ break;
+ }
+
+ /*
+ * comedi_handle_events() cannot be used in this driver. The (*cancel)
+ * operation would unlink the urb.
+ */
+ if (async->events & COMEDI_CB_CANCEL_MASK)
+ usbdux_ao_stop(dev, 0);
+
+ comedi_event(dev, s);
+}
+
+static int usbdux_submit_urbs(struct comedi_device *dev,
+ struct urb **urbs, int num_urbs,
+ int input_urb)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb;
+ int ret;
+ int i;
+
+ /* Submit all URBs and start the transfer on the bus */
+ for (i = 0; i < num_urbs; i++) {
+ urb = urbs[i];
+
+ /* in case of a resubmission after an unlink... */
+ if (input_urb)
+ urb->interval = devpriv->ai_interval;
+ urb->context = dev;
+ urb->dev = usb;
+ urb->status = 0;
+ urb->transfer_flags = URB_ISO_ASAP;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int usbdux_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ struct usbdux_private *this_usbduxsub = dev->private;
+ int err = 0, i;
+ unsigned int tmp_timer;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ if (this_usbduxsub->high_speed) {
+ /*
+ * In high speed mode microframes are possible.
+ * However, during one microframe we can roughly
+ * sample one channel. Thus, the more channels
+ * are in the channel list the more time we need.
+ */
+ i = 1;
+ /* find a power of 2 for the number of channels */
+ while (i < (cmd->chanlist_len))
+ i = i * 2;
+
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ 1000000 / 8 * i);
+ /* now calc the real sampling rate with all the
+ * rounding errors */
+ tmp_timer =
+ ((unsigned int)(cmd->scan_begin_arg / 125000)) *
+ 125000;
+ } else {
+ /* full speed */
+ /* 1kHz scans every USB frame */
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ 1000000);
+ /*
+ * calc the real sampling rate with the rounding errors
+ */
+ tmp_timer = ((unsigned int)(cmd->scan_begin_arg /
+ 1000000)) * 1000000;
+ }
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg,
+ tmp_timer);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ return 0;
+}
+
+/*
+ * creates the ADC command for the MAX1271
+ * range is the range value from comedi
+ */
+static uint8_t create_adc_command(unsigned int chan, unsigned int range)
+{
+ uint8_t p = (range <= 1);
+ uint8_t r = ((range % 2) == 0);
+
+ return (chan << 4) | ((p == 1) << 2) | ((r == 1) << 3);
+}
+
+static int send_dux_commands(struct comedi_device *dev, unsigned int cmd_type)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ int nsent;
+
+ devpriv->dux_commands[0] = cmd_type;
+
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, 1),
+ devpriv->dux_commands, SIZEOFDUXBUFFER,
+ &nsent, BULK_TIMEOUT);
+}
+
+static int receive_dux_commands(struct comedi_device *dev, unsigned int command)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+ int nrec;
+ int i;
+
+ for (i = 0; i < RETRIES; i++) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, 8),
+ devpriv->insn_buf, SIZEINSNBUF,
+ &nrec, BULK_TIMEOUT);
+ if (ret < 0)
+ return ret;
+ if (le16_to_cpu(devpriv->insn_buf[0]) == command)
+ return ret;
+ }
+ /* command not received */
+ return -EFAULT;
+}
+
+static int usbdux_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ if (!devpriv->ai_cmd_running) {
+ devpriv->ai_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ goto ai_trig_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ ret = -EBUSY;
+ }
+
+ai_trig_exit:
+ up(&devpriv->sem);
+ return ret;
+}
+
+static int usbdux_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int len = cmd->chanlist_len;
+ int ret = -EBUSY;
+ int i;
+
+ /* block other CPUs from starting an ai_cmd */
+ down(&devpriv->sem);
+
+ if (devpriv->ai_cmd_running)
+ goto ai_cmd_exit;
+
+ devpriv->dux_commands[1] = len;
+ for (i = 0; i < len; ++i) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ devpriv->dux_commands[i + 2] = create_adc_command(chan, range);
+ }
+
+ ret = send_dux_commands(dev, USBDUX_CMD_MULT_AI);
+ if (ret < 0)
+ goto ai_cmd_exit;
+
+ if (devpriv->high_speed) {
+ /*
+ * every channel gets a time window of 125us. Thus, if we
+ * sample all 8 channels we need 1ms. If we sample only one
+ * channel we need only 125us
+ */
+ devpriv->ai_interval = 1;
+ /* find a power of 2 for the interval */
+ while (devpriv->ai_interval < len)
+ devpriv->ai_interval *= 2;
+
+ devpriv->ai_timer = cmd->scan_begin_arg /
+ (125000 * devpriv->ai_interval);
+ } else {
+ /* interval always 1ms */
+ devpriv->ai_interval = 1;
+ devpriv->ai_timer = cmd->scan_begin_arg / 1000000;
+ }
+ if (devpriv->ai_timer < 1) {
+ ret = -EINVAL;
+ goto ai_cmd_exit;
+ }
+
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ai_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ /* fixme: unlink here?? */
+ goto ai_cmd_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ /* TRIG_INT */
+ /* don't enable the acquision operation */
+ /* wait for an internal signal */
+ s->async->inttrig = usbdux_ai_inttrig;
+ }
+
+ai_cmd_exit:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+/* Mode 0 is used to get a single conversion on demand */
+static int usbdux_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int ret = -EBUSY;
+ int i;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ai_cmd_running)
+ goto ai_read_exit;
+
+ /* set command for the first channel */
+ devpriv->dux_commands[1] = create_adc_command(chan, range);
+
+ /* adc commands */
+ ret = send_dux_commands(dev, USBDUX_CMD_SINGLE_AI);
+ if (ret < 0)
+ goto ai_read_exit;
+
+ for (i = 0; i < insn->n; i++) {
+ ret = receive_dux_commands(dev, USBDUX_CMD_SINGLE_AI);
+ if (ret < 0)
+ goto ai_read_exit;
+
+ val = le16_to_cpu(devpriv->insn_buf[1]);
+
+ /* bipolar data is two's-complement */
+ if (comedi_range_is_bipolar(s, range))
+ val ^= ((s->maxdata + 1) >> 1);
+
+ data[i] = val;
+ }
+
+ai_read_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+ ret = comedi_readback_insn_read(dev, s, insn, data);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int usbdux_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = s->readback[chan];
+ __le16 *p = (__le16 *)&devpriv->dux_commands[2];
+ int ret = -EBUSY;
+ int i;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ao_cmd_running)
+ goto ao_write_exit;
+
+ /* number of channels: 1 */
+ devpriv->dux_commands[1] = 1;
+ /* channel number */
+ devpriv->dux_commands[4] = chan << 6;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* one 16 bit value */
+ *p = cpu_to_le16(val);
+
+ ret = send_dux_commands(dev, USBDUX_CMD_AO);
+ if (ret < 0)
+ goto ao_write_exit;
+
+ s->readback[chan] = val;
+ }
+
+ao_write_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ if (!devpriv->ao_cmd_running) {
+ devpriv->ao_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ goto ao_trig_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ ret = -EBUSY;
+ }
+
+ao_trig_exit:
+ up(&devpriv->sem);
+ return ret;
+}
+
+static int usbdux_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ struct usbdux_private *this_usbduxsub = dev->private;
+ int err = 0;
+ unsigned int flags;
+
+ if (!this_usbduxsub)
+ return -EFAULT;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+
+ if (0) { /* (this_usbduxsub->high_speed) */
+ /* the sampling rate is set by the coversion rate */
+ flags = TRIG_FOLLOW;
+ } else {
+ /* start a new scan (output at once) with a timer */
+ flags = TRIG_TIMER;
+ }
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags);
+
+ if (0) { /* (this_usbduxsub->high_speed) */
+ /*
+ * in usb-2.0 only one conversion it transmitted
+ * but with 8kHz/n
+ */
+ flags = TRIG_TIMER;
+ } else {
+ /*
+ * all conversion events happen simultaneously with
+ * a rate of 1kHz/n
+ */
+ flags = TRIG_NOW;
+ }
+ err |= comedi_check_trigger_src(&cmd->convert_src, flags);
+
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 1000000);
+ }
+
+ /* not used now, is for later use */
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 125000);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ return 0;
+}
+
+static int usbdux_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret = -EBUSY;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ao_cmd_running)
+ goto ao_cmd_exit;
+
+ /* we count in steps of 1ms (125us) */
+ /* 125us mode not used yet */
+ if (0) { /* (devpriv->high_speed) */
+ /* 125us */
+ /* timing of the conversion itself: every 125 us */
+ devpriv->ao_timer = cmd->convert_arg / 125000;
+ } else {
+ /* 1ms */
+ /* timing of the scan: we get all channels at once */
+ devpriv->ao_timer = cmd->scan_begin_arg / 1000000;
+ if (devpriv->ao_timer < 1) {
+ ret = -EINVAL;
+ goto ao_cmd_exit;
+ }
+ }
+
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ao_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ /* fixme: unlink here?? */
+ goto ao_cmd_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ /* TRIG_INT */
+ /* submit the urbs later */
+ /* wait for an internal signal */
+ s->async->inttrig = usbdux_ao_inttrig;
+ }
+
+ao_cmd_exit:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int usbdux_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * We don't tell the firmware here as it would take 8 frames
+ * to submit the information. We do it in the insn_bits.
+ */
+ return insn->n;
+}
+
+static int usbdux_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+
+ comedi_dio_update_state(s, data);
+
+ /* Always update the hardware. See the (*insn_config). */
+ devpriv->dux_commands[1] = s->io_bits;
+ devpriv->dux_commands[2] = s->state;
+
+ /*
+ * This command also tells the firmware to return
+ * the digital input lines.
+ */
+ ret = send_dux_commands(dev, USBDUX_CMD_DIO_BITS);
+ if (ret < 0)
+ goto dio_exit;
+ ret = receive_dux_commands(dev, USBDUX_CMD_DIO_BITS);
+ if (ret < 0)
+ goto dio_exit;
+
+ data[1] = le16_to_cpu(devpriv->insn_buf[1]);
+
+dio_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_counter_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret = 0;
+ int i;
+
+ down(&devpriv->sem);
+
+ for (i = 0; i < insn->n; i++) {
+ ret = send_dux_commands(dev, USBDUX_CMD_TIMER_RD);
+ if (ret < 0)
+ goto counter_read_exit;
+ ret = receive_dux_commands(dev, USBDUX_CMD_TIMER_RD);
+ if (ret < 0)
+ goto counter_read_exit;
+
+ data[i] = le16_to_cpu(devpriv->insn_buf[chan + 1]);
+ }
+
+counter_read_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_counter_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ __le16 *p = (__le16 *)&devpriv->dux_commands[2];
+ int ret = 0;
+ int i;
+
+ down(&devpriv->sem);
+
+ devpriv->dux_commands[1] = chan;
+
+ for (i = 0; i < insn->n; i++) {
+ *p = cpu_to_le16(data[i]);
+
+ ret = send_dux_commands(dev, USBDUX_CMD_TIMER_WR);
+ if (ret < 0)
+ break;
+ }
+
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_counter_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ /* nothing to do so far */
+ return 2;
+}
+
+static void usbduxsub_unlink_pwm_urbs(struct comedi_device *dev)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ usb_kill_urb(devpriv->pwm_urb);
+}
+
+static void usbdux_pwm_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ if (do_unlink)
+ usbduxsub_unlink_pwm_urbs(dev);
+
+ devpriv->pwm_cmd_running = 0;
+}
+
+static int usbdux_pwm_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbdux_pwm_stop(dev, devpriv->pwm_cmd_running);
+ ret = send_dux_commands(dev, USBDUX_CMD_PWM_OFF);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void usbduxsub_pwm_irq(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /*
+ * after an unlink command, unplug, ... etc
+ * no unlink needed here. Already shutting down.
+ */
+ if (devpriv->pwm_cmd_running)
+ usbdux_pwm_stop(dev, 0);
+
+ return;
+
+ default:
+ /* a real error */
+ if (devpriv->pwm_cmd_running) {
+ dev_err(dev->class_dev,
+ "Non-zero urb status received in pwm intr context: %d\n",
+ urb->status);
+ usbdux_pwm_stop(dev, 0);
+ }
+ return;
+ }
+
+ /* are we actually running? */
+ if (!devpriv->pwm_cmd_running)
+ return;
+
+ urb->transfer_buffer_length = devpriv->pwm_buf_sz;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ if (devpriv->pwm_cmd_running) {
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "pwm urb resubm failed in int-cont. ret=%d",
+ ret);
+ if (ret == -EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handling!\n");
+
+ /* don't do an unlink here */
+ usbdux_pwm_stop(dev, 0);
+ }
+ }
+}
+
+static int usbduxsub_submit_pwm_urbs(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb = devpriv->pwm_urb;
+
+ /* in case of a resubmission after an unlink... */
+ usb_fill_bulk_urb(urb, usb, usb_sndbulkpipe(usb, 4),
+ urb->transfer_buffer,
+ devpriv->pwm_buf_sz,
+ usbduxsub_pwm_irq,
+ dev);
+
+ return usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int usbdux_pwm_period(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int period)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int fx2delay = 255;
+
+ if (period < MIN_PWM_PERIOD)
+ return -EAGAIN;
+
+ fx2delay = (period / (6 * 512 * 1000 / 33)) - 6;
+ if (fx2delay > 255)
+ return -EAGAIN;
+
+ devpriv->pwm_delay = fx2delay;
+ devpriv->pwm_period = period;
+
+ return 0;
+}
+
+static int usbdux_pwm_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int ret = 0;
+
+ down(&devpriv->sem);
+
+ if (devpriv->pwm_cmd_running)
+ goto pwm_start_exit;
+
+ devpriv->dux_commands[1] = devpriv->pwm_delay;
+ ret = send_dux_commands(dev, USBDUX_CMD_PWM_ON);
+ if (ret < 0)
+ goto pwm_start_exit;
+
+ /* initialise the buffer */
+ memset(devpriv->pwm_urb->transfer_buffer, 0, devpriv->pwm_buf_sz);
+
+ devpriv->pwm_cmd_running = 1;
+ ret = usbduxsub_submit_pwm_urbs(dev);
+ if (ret < 0)
+ devpriv->pwm_cmd_running = 0;
+
+pwm_start_exit:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void usbdux_pwm_pattern(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int value,
+ unsigned int sign)
+{
+ struct usbdux_private *devpriv = dev->private;
+ char pwm_mask = (1 << chan); /* DIO bit for the PWM data */
+ char sgn_mask = (16 << chan); /* DIO bit for the sign */
+ char *buf = (char *)(devpriv->pwm_urb->transfer_buffer);
+ int szbuf = devpriv->pwm_buf_sz;
+ int i;
+
+ for (i = 0; i < szbuf; i++) {
+ char c = *buf;
+
+ c &= ~pwm_mask;
+ if (i < value)
+ c |= pwm_mask;
+ if (!sign)
+ c &= ~sgn_mask;
+ else
+ c |= sgn_mask;
+ *buf++ = c;
+ }
+}
+
+static int usbdux_pwm_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * It doesn't make sense to support more than one value here
+ * because it would just overwrite the PWM buffer.
+ */
+ if (insn->n != 1)
+ return -EINVAL;
+
+ /*
+ * The sign is set via a special INSN only, this gives us 8 bits
+ * for normal operation, sign is 0 by default.
+ */
+ usbdux_pwm_pattern(dev, s, chan, data[0], 0);
+
+ return insn->n;
+}
+
+static int usbdux_pwm_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ /*
+ * if not zero the PWM is limited to a certain time which is
+ * not supported here
+ */
+ if (data[1] != 0)
+ return -EINVAL;
+ return usbdux_pwm_start(dev, s);
+ case INSN_CONFIG_DISARM:
+ return usbdux_pwm_cancel(dev, s);
+ case INSN_CONFIG_GET_PWM_STATUS:
+ data[1] = devpriv->pwm_cmd_running;
+ return 0;
+ case INSN_CONFIG_PWM_SET_PERIOD:
+ return usbdux_pwm_period(dev, s, data[1]);
+ case INSN_CONFIG_PWM_GET_PERIOD:
+ data[1] = devpriv->pwm_period;
+ return 0;
+ case INSN_CONFIG_PWM_SET_H_BRIDGE:
+ /*
+ * data[1] = value
+ * data[2] = sign (for a relay)
+ */
+ usbdux_pwm_pattern(dev, s, chan, data[1], (data[2] != 0));
+ return 0;
+ case INSN_CONFIG_PWM_GET_H_BRIDGE:
+ /* values are not kept in this driver, nothing to return here */
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+static int usbdux_firmware_upload(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ uint8_t *buf;
+ uint8_t *tmp;
+ int ret;
+
+ if (!data)
+ return 0;
+
+ if (size > USBDUX_FIRMWARE_MAX_LEN) {
+ dev_err(dev->class_dev,
+ "usbdux firmware binary it too large for FX2.\n");
+ return -ENOMEM;
+ }
+
+ /* we generate a local buffer for the firmware */
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* we need a malloc'ed buffer for usb_control_msg() */
+ tmp = kmalloc(1, GFP_KERNEL);
+ if (!tmp) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* stop the current firmware on the device */
+ *tmp = 1; /* 7f92 to one */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUX_FIRMWARE_CMD,
+ VENDOR_DIR_OUT,
+ USBDUX_CPU_CS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "can not stop firmware\n");
+ goto done;
+ }
+
+ /* upload the new firmware to the device */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUX_FIRMWARE_CMD,
+ VENDOR_DIR_OUT,
+ 0, 0x0000,
+ buf, size,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "firmware upload failed\n");
+ goto done;
+ }
+
+ /* start the new firmware on the device */
+ *tmp = 0; /* 7f92 to zero */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUX_FIRMWARE_CMD,
+ VENDOR_DIR_OUT,
+ USBDUX_CPU_CS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0)
+ dev_err(dev->class_dev, "can not start firmware\n");
+
+done:
+ kfree(tmp);
+ kfree(buf);
+ return ret;
+}
+
+static int usbdux_alloc_usb_buffers(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ devpriv->dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL);
+ devpriv->in_buf = kzalloc(SIZEINBUF, GFP_KERNEL);
+ devpriv->insn_buf = kzalloc(SIZEINSNBUF, GFP_KERNEL);
+ devpriv->ai_urbs = kcalloc(devpriv->n_ai_urbs, sizeof(void *),
+ GFP_KERNEL);
+ devpriv->ao_urbs = kcalloc(devpriv->n_ao_urbs, sizeof(void *),
+ GFP_KERNEL);
+ if (!devpriv->dux_commands || !devpriv->in_buf || !devpriv->insn_buf ||
+ !devpriv->ai_urbs || !devpriv->ao_urbs)
+ return -ENOMEM;
+
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ai_urbs[i] = urb;
+
+ urb->dev = usb;
+ urb->context = dev;
+ urb->pipe = usb_rcvisocpipe(usb, 6);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEINBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+
+ urb->complete = usbduxsub_ai_isoc_irq;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEINBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEINBUF;
+ }
+
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ao_urbs[i] = urb;
+
+ urb->dev = usb;
+ urb->context = dev;
+ urb->pipe = usb_sndisocpipe(usb, 2);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEOUTBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+
+ urb->complete = usbduxsub_ao_isoc_irq;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEOUTBUF;
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ }
+
+ /* pwm */
+ if (devpriv->pwm_buf_sz) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->pwm_urb = urb;
+
+ /* max bulk ep size in high speed */
+ urb->transfer_buffer = kzalloc(devpriv->pwm_buf_sz,
+ GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void usbdux_free_usb_buffers(struct comedi_device *dev)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ urb = devpriv->pwm_urb;
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ if (devpriv->ao_urbs) {
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ urb = devpriv->ao_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ao_urbs);
+ }
+ if (devpriv->ai_urbs) {
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ urb = devpriv->ai_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ai_urbs);
+ }
+ kfree(devpriv->insn_buf);
+ kfree(devpriv->in_buf);
+ kfree(devpriv->dux_commands);
+}
+
+static int usbdux_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+
+ usb_set_intfdata(intf, devpriv);
+
+ devpriv->high_speed = (usb->speed == USB_SPEED_HIGH);
+ if (devpriv->high_speed) {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSHIGH;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSHIGH;
+ devpriv->pwm_buf_sz = 512;
+ } else {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSFULL;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSFULL;
+ }
+
+ ret = usbdux_alloc_usb_buffers(dev);
+ if (ret)
+ return ret;
+
+ /* setting to alternate setting 3: enabling iso ep and bulk ep. */
+ ret = usb_set_interface(usb, intf->altsetting->desc.bInterfaceNumber,
+ 3);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "could not set alternate setting 3 in high speed\n");
+ return ret;
+ }
+
+ ret = comedi_load_firmware(dev, &usb->dev, USBDUX_FIRMWARE,
+ usbdux_firmware_upload, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 5 : 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
+ s->n_chan = 8;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 8;
+ s->range_table = &range_usbdux_ai_range;
+ s->insn_read = usbdux_ai_insn_read;
+ s->do_cmdtest = usbdux_ai_cmdtest;
+ s->do_cmd = usbdux_ai_cmd;
+ s->cancel = usbdux_ai_cancel;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ dev->write_subdev = s;
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = s->n_chan;
+ s->range_table = &range_usbdux_ao_range;
+ s->do_cmdtest = usbdux_ao_cmdtest;
+ s->do_cmd = usbdux_ao_cmd;
+ s->cancel = usbdux_ao_cancel;
+ s->insn_read = usbdux_ao_insn_read;
+ s->insn_write = usbdux_ao_insn_write;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = usbdux_dio_insn_bits;
+ s->insn_config = usbdux_dio_insn_config;
+
+ /* Counter subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->insn_read = usbdux_counter_read;
+ s->insn_write = usbdux_counter_write;
+ s->insn_config = usbdux_counter_config;
+
+ if (devpriv->high_speed) {
+ /* PWM subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE;
+ s->n_chan = 8;
+ s->maxdata = devpriv->pwm_buf_sz;
+ s->insn_write = usbdux_pwm_write;
+ s->insn_config = usbdux_pwm_config;
+
+ usbdux_pwm_period(dev, s, PWM_DEFAULT_PERIOD);
+ }
+
+ return 0;
+}
+
+static void usbdux_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usbdux_private *devpriv = dev->private;
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ /* force unlink all urbs */
+ usbdux_pwm_stop(dev, 1);
+ usbdux_ao_stop(dev, 1);
+ usbdux_ai_stop(dev, 1);
+
+ usbdux_free_usb_buffers(dev);
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver usbdux_driver = {
+ .driver_name = "usbdux",
+ .module = THIS_MODULE,
+ .auto_attach = usbdux_auto_attach,
+ .detach = usbdux_detach,
+};
+
+static int usbdux_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &usbdux_driver, 0);
+}
+
+static const struct usb_device_id usbdux_usb_table[] = {
+ { USB_DEVICE(0x13d8, 0x0001) },
+ { USB_DEVICE(0x13d8, 0x0002) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbdux_usb_table);
+
+static struct usb_driver usbdux_usb_driver = {
+ .name = "usbdux",
+ .probe = usbdux_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+ .id_table = usbdux_usb_table,
+};
+module_comedi_usb_driver(usbdux_driver, usbdux_usb_driver);
+
+MODULE_AUTHOR("Bernd Porr, BerndPorr@f2s.com");
+MODULE_DESCRIPTION("Stirling/ITL USB-DUX -- Bernd.Porr@f2s.com");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(USBDUX_FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/usbduxfast.c b/drivers/staging/comedi/drivers/usbduxfast.c
new file mode 100644
index 000000000..d90dc5998
--- /dev/null
+++ b/drivers/staging/comedi/drivers/usbduxfast.c
@@ -0,0 +1,1107 @@
+/*
+ * Copyright (C) 2004-2014 Bernd Porr, mail@berndporr.me.uk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: usbduxfast
+ * Description: University of Stirling USB DAQ & INCITE Technology Limited
+ * Devices: [ITL] USB-DUX-FAST (usbduxfast)
+ * Author: Bernd Porr <mail@berndporr.me.uk>
+ * Updated: 10 Oct 2014
+ * Status: stable
+ */
+
+/*
+ * I must give credit here to Chris Baugher who
+ * wrote the driver for AT-MIO-16d. I used some parts of this
+ * driver. I also must give credits to David Brownell
+ * who supported me with the USB development.
+ *
+ * Bernd Porr
+ *
+ *
+ * Revision history:
+ * 0.9: Dropping the first data packet which seems to be from the last transfer.
+ * Buffer overflows in the FX2 are handed over to comedi.
+ * 0.92: Dropping now 4 packets. The quad buffer has to be emptied.
+ * Added insn command basically for testing. Sample rate is
+ * 1MHz/16ch=62.5kHz
+ * 0.99: Ian Abbott pointed out a bug which has been corrected. Thanks!
+ * 0.99a: added external trigger.
+ * 1.00: added firmware kernel request to the driver which fixed
+ * udev coldplug problem
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/fcntl.h>
+#include <linux/compiler.h>
+#include "../comedi_usb.h"
+
+/*
+ * timeout for the USB-transfer
+ */
+#define EZTIMEOUT 30
+
+/*
+ * constants for "firmware" upload and download
+ */
+#define FIRMWARE "usbduxfast_firmware.bin"
+#define FIRMWARE_MAX_LEN 0x2000
+#define USBDUXFASTSUB_FIRMWARE 0xA0
+#define VENDOR_DIR_IN 0xC0
+#define VENDOR_DIR_OUT 0x40
+
+/*
+ * internal addresses of the 8051 processor
+ */
+#define USBDUXFASTSUB_CPUCS 0xE600
+
+/*
+ * max lenghth of the transfer-buffer for software upload
+ */
+#define TB_LEN 0x2000
+
+/*
+ * input endpoint number
+ */
+#define BULKINEP 6
+
+/*
+ * endpoint for the A/D channellist: bulk OUT
+ */
+#define CHANNELLISTEP 4
+
+/*
+ * number of channels
+ */
+#define NUMCHANNELS 32
+
+/*
+ * size of the waveform descriptor
+ */
+#define WAVESIZE 0x20
+
+/*
+ * size of one A/D value
+ */
+#define SIZEADIN (sizeof(int16_t))
+
+/*
+ * size of the input-buffer IN BYTES
+ */
+#define SIZEINBUF 512
+
+/*
+ * 16 bytes
+ */
+#define SIZEINSNBUF 512
+
+/*
+ * size of the buffer for the dux commands in bytes
+ */
+#define SIZEOFDUXBUF 256
+
+/*
+ * number of in-URBs which receive the data: min=5
+ */
+#define NUMOFINBUFFERSHIGH 10
+
+/*
+ * min delay steps for more than one channel
+ * basically when the mux gives up ;-)
+ *
+ * steps at 30MHz in the FX2
+ */
+#define MIN_SAMPLING_PERIOD 9
+
+/*
+ * max number of 1/30MHz delay steps
+ */
+#define MAX_SAMPLING_PERIOD 500
+
+/*
+ * number of received packets to ignore before we start handing data
+ * over to comedi, it's quad buffering and we have to ignore 4 packets
+ */
+#define PACKETS_TO_IGNORE 4
+
+/*
+ * comedi constants
+ */
+static const struct comedi_lrange range_usbduxfast_ai_range = {
+ 2, {
+ BIP_RANGE(0.75),
+ BIP_RANGE(0.5)
+ }
+};
+
+/*
+ * private structure of one subdevice
+ *
+ * this is the structure which holds all the data of this driver
+ * one sub device just now: A/D
+ */
+struct usbduxfast_private {
+ struct urb *urb; /* BULK-transfer handling: urb */
+ uint8_t *duxbuf;
+ int8_t *inbuf;
+ short int ai_cmd_running; /* asynchronous command is running */
+ int ignore; /* counter which ignores the first
+ buffers */
+ struct semaphore sem;
+};
+
+/*
+ * bulk transfers to usbduxfast
+ */
+#define SENDADCOMMANDS 0
+#define SENDINITEP6 1
+
+static int usbduxfast_send_cmd(struct comedi_device *dev, int cmd_type)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+ int nsent;
+ int ret;
+
+ devpriv->duxbuf[0] = cmd_type;
+
+ ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, CHANNELLISTEP),
+ devpriv->duxbuf, SIZEOFDUXBUF,
+ &nsent, 10000);
+ if (ret < 0)
+ dev_err(dev->class_dev,
+ "could not transmit command to the usb-device, err=%d\n",
+ ret);
+ return ret;
+}
+
+static void usbduxfast_cmd_data(struct comedi_device *dev, int index,
+ uint8_t len, uint8_t op, uint8_t out,
+ uint8_t log)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+
+ /* Set the GPIF bytes, the first byte is the command byte */
+ devpriv->duxbuf[1 + 0x00 + index] = len;
+ devpriv->duxbuf[1 + 0x08 + index] = op;
+ devpriv->duxbuf[1 + 0x10 + index] = out;
+ devpriv->duxbuf[1 + 0x18 + index] = log;
+}
+
+static int usbduxfast_ai_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+
+ /* stop aquistion */
+ devpriv->ai_cmd_running = 0;
+
+ if (do_unlink && devpriv->urb) {
+ /* kill the running transfer */
+ usb_kill_urb(devpriv->urb);
+ }
+
+ return 0;
+}
+
+static int usbduxfast_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ int ret;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ down(&devpriv->sem);
+ ret = usbduxfast_ai_stop(dev, 1);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void usbduxfast_ai_handle_urb(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct urb *urb)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int ret;
+
+ if (devpriv->ignore) {
+ devpriv->ignore--;
+ } else {
+ unsigned int nsamples;
+
+ nsamples = comedi_bytes_to_samples(s, urb->actual_length);
+ nsamples = comedi_nsamples_left(s, nsamples);
+ comedi_buf_write_samples(s, urb->transfer_buffer, nsamples);
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ /* if command is still running, resubmit urb for BULK transfer */
+ if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "urb resubm failed: %d", ret);
+ async->events |= COMEDI_CB_ERROR;
+ }
+ }
+}
+
+static void usbduxfast_ai_interrupt(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct usbduxfast_private *devpriv = dev->private;
+
+ /* exit if not running a command, do not resubmit urb */
+ if (!devpriv->ai_cmd_running)
+ return;
+
+ switch (urb->status) {
+ case 0:
+ usbduxfast_ai_handle_urb(dev, s, urb);
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* after an unlink command, unplug, ... etc */
+ async->events |= COMEDI_CB_ERROR;
+ break;
+
+ default:
+ /* a real error */
+ dev_err(dev->class_dev,
+ "non-zero urb status received in ai intr context: %d\n",
+ urb->status);
+ async->events |= COMEDI_CB_ERROR;
+ break;
+ }
+
+ /*
+ * comedi_handle_events() cannot be used in this driver. The (*cancel)
+ * operation would unlink the urb.
+ */
+ if (async->events & COMEDI_CB_CANCEL_MASK)
+ usbduxfast_ai_stop(dev, 0);
+
+ comedi_event(dev, s);
+}
+
+static int usbduxfast_submit_urb(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+ int ret;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ usb_fill_bulk_urb(devpriv->urb, usb, usb_rcvbulkpipe(usb, BULKINEP),
+ devpriv->inbuf, SIZEINBUF,
+ usbduxfast_ai_interrupt, dev);
+
+ ret = usb_submit_urb(devpriv->urb, GFP_ATOMIC);
+ if (ret) {
+ dev_err(dev->class_dev, "usb_submit_urb error %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int usbduxfast_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ long int steps, tmp;
+ int min_sample_period;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_EXT | TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= comedi_check_trigger_is_unique(cmd->convert_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /* can't have external stop and start triggers at once */
+ if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (!cmd->chanlist_len)
+ err |= -EINVAL;
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->chanlist_len == 1)
+ min_sample_period = 1;
+ else
+ min_sample_period = MIN_SAMPLING_PERIOD;
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ steps = cmd->convert_arg * 30;
+ if (steps < (min_sample_period * 1000))
+ steps = min_sample_period * 1000;
+
+ if (steps > (MAX_SAMPLING_PERIOD * 1000))
+ steps = MAX_SAMPLING_PERIOD * 1000;
+
+ /* calc arg again */
+ tmp = steps / 30;
+ err |= comedi_check_trigger_arg_is(&cmd->convert_arg, tmp);
+ }
+
+ /* stop source */
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ /*
+ * TRIG_EXT doesn't care since it doesn't trigger
+ * off a numbered channel
+ */
+ default:
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ return 0;
+}
+
+static int usbduxfast_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ if (!devpriv->ai_cmd_running) {
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxfast_submit_urb(dev);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "urbSubmit: err=%d\n", ret);
+ devpriv->ai_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ dev_err(dev->class_dev, "ai is already running\n");
+ }
+ up(&devpriv->sem);
+ return 1;
+}
+
+static int usbduxfast_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int chan, gain, rngmask = 0xff;
+ int i, j, ret;
+ int result;
+ long steps, steps_tmp;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ down(&devpriv->sem);
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_dev, "ai_cmd not possible\n");
+ up(&devpriv->sem);
+ return -EBUSY;
+ }
+
+ /*
+ * ignore the first buffers from the device if there
+ * is an error condition
+ */
+ devpriv->ignore = PACKETS_TO_IGNORE;
+
+ gain = CR_RANGE(cmd->chanlist[0]);
+ for (i = 0; i < cmd->chanlist_len; ++i) {
+ chan = CR_CHAN(cmd->chanlist[i]);
+ if (chan != i) {
+ dev_err(dev->class_dev,
+ "channels are not consecutive\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if ((gain != CR_RANGE(cmd->chanlist[i]))
+ && (cmd->chanlist_len > 3)) {
+ dev_err(dev->class_dev,
+ "gain must be the same for all channels\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if (i >= NUMCHANNELS) {
+ dev_err(dev->class_dev, "chanlist too long\n");
+ break;
+ }
+ }
+ steps = 0;
+ if (cmd->convert_src == TRIG_TIMER)
+ steps = (cmd->convert_arg * 30) / 1000;
+
+ if ((steps < MIN_SAMPLING_PERIOD) && (cmd->chanlist_len != 1)) {
+ dev_err(dev->class_dev,
+ "steps=%ld, scan_begin_arg=%d. Not properly tested by cmdtest?\n",
+ steps, cmd->scan_begin_arg);
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if (steps > MAX_SAMPLING_PERIOD) {
+ dev_err(dev->class_dev, "sampling rate too low\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if ((cmd->start_src == TRIG_EXT) && (cmd->chanlist_len != 1)
+ && (cmd->chanlist_len != 16)) {
+ dev_err(dev->class_dev,
+ "TRIG_EXT only with 1 or 16 channels possible\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+
+ switch (cmd->chanlist_len) {
+ case 1:
+ /*
+ * one channel
+ */
+
+ if (CR_RANGE(cmd->chanlist[0]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /*
+ * for external trigger: looping in this state until
+ * the RDY0 pin becomes zero
+ */
+
+ /* we loop here until ready has been set */
+ if (cmd->start_src == TRIG_EXT) {
+ /* branch back to state 0 */
+ /* deceision state w/o data */
+ /* RDY0 = 0 */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x01, rngmask, 0x00);
+ } else { /* we just proceed to state 1 */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x00, rngmask, 0x00);
+ }
+
+ if (steps < MIN_SAMPLING_PERIOD) {
+ /* for fast single channel aqu without mux */
+ if (steps <= 1) {
+ /*
+ * we just stay here at state 1 and rexecute
+ * the same state this gives us 30MHz sampling
+ * rate
+ */
+
+ /* branch back to state 1 */
+ /* deceision state with data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 1,
+ 0x89, 0x03, rngmask, 0xff);
+ } else {
+ /*
+ * we loop through two states: data and delay
+ * max rate is 15MHz
+ */
+ /* data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 1, steps - 1,
+ 0x02, rngmask, 0x00);
+
+ /* branch back to state 1 */
+ /* deceision state w/o data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 2,
+ 0x09, 0x01, rngmask, 0xff);
+ }
+ } else {
+ /*
+ * we loop through 3 states: 2x delay and 1x data
+ * this gives a min sampling rate of 60kHz
+ */
+
+ /* we have 1 state with duration 1 */
+ steps = steps - 1;
+
+ /* do the first part of the delay */
+ usbduxfast_cmd_data(dev, 1,
+ steps / 2, 0x00, rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 2, steps - steps / 2,
+ 0x00, rngmask, 0x00);
+
+ /* get the data and branch back */
+
+ /* branch back to state 1 */
+ /* deceision state w data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 3,
+ 0x09, 0x03, rngmask, 0xff);
+ }
+ break;
+
+ case 2:
+ /*
+ * two channels
+ * commit data to the FIFO
+ */
+
+ if (CR_RANGE(cmd->chanlist[0]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* data */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x02, rngmask, 0x00);
+
+ /* we have 1 state with duration 1: state 0 */
+ steps_tmp = steps - 1;
+
+ if (CR_RANGE(cmd->chanlist[1]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* do the first part of the delay */
+ /* count */
+ usbduxfast_cmd_data(dev, 1, steps_tmp / 2,
+ 0x00, 0xfe & rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 2, steps_tmp - steps_tmp / 2,
+ 0x00, rngmask, 0x00);
+
+ /* data */
+ usbduxfast_cmd_data(dev, 3, 0x01, 0x02, rngmask, 0x00);
+
+ /*
+ * we have 2 states with duration 1: step 6 and
+ * the IDLE state
+ */
+ steps_tmp = steps - 2;
+
+ if (CR_RANGE(cmd->chanlist[0]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* do the first part of the delay */
+ /* reset */
+ usbduxfast_cmd_data(dev, 4, steps_tmp / 2,
+ 0x00, (0xff - 0x02) & rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2,
+ 0x00, rngmask, 0x00);
+
+ usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00);
+ break;
+
+ case 3:
+ /*
+ * three channels
+ */
+ for (j = 0; j < 1; j++) {
+ int index = j * 2;
+
+ if (CR_RANGE(cmd->chanlist[j]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+ /*
+ * commit data to the FIFO and do the first part
+ * of the delay
+ */
+ /* data */
+ /* no change */
+ usbduxfast_cmd_data(dev, index, steps / 2,
+ 0x02, rngmask, 0x00);
+
+ if (CR_RANGE(cmd->chanlist[j + 1]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* do the second part of the delay */
+ /* no data */
+ /* count */
+ usbduxfast_cmd_data(dev, index + 1, steps - steps / 2,
+ 0x00, 0xfe & rngmask, 0x00);
+ }
+
+ /* 2 steps with duration 1: the idele step and step 6: */
+ steps_tmp = steps - 2;
+
+ /* commit data to the FIFO and do the first part of the delay */
+ /* data */
+ usbduxfast_cmd_data(dev, 4, steps_tmp / 2,
+ 0x02, rngmask, 0x00);
+
+ if (CR_RANGE(cmd->chanlist[0]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* do the second part of the delay */
+ /* no data */
+ /* reset */
+ usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2,
+ 0x00, (0xff - 0x02) & rngmask, 0x00);
+
+ usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00);
+ break;
+
+ case 16:
+ if (CR_RANGE(cmd->chanlist[0]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ if (cmd->start_src == TRIG_EXT) {
+ /*
+ * we loop here until ready has been set
+ */
+
+ /* branch back to state 0 */
+ /* deceision state w/o data */
+ /* reset */
+ /* RDY0 = 0 */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x01,
+ (0xff - 0x02) & rngmask, 0x00);
+ } else {
+ /*
+ * we just proceed to state 1
+ */
+
+ /* 30us reset pulse */
+ /* reset */
+ usbduxfast_cmd_data(dev, 0, 0xff, 0x00,
+ (0xff - 0x02) & rngmask, 0x00);
+ }
+
+ /* commit data to the FIFO */
+ /* data */
+ usbduxfast_cmd_data(dev, 1, 0x01, 0x02, rngmask, 0x00);
+
+ /* we have 2 states with duration 1 */
+ steps = steps - 2;
+
+ /* do the first part of the delay */
+ usbduxfast_cmd_data(dev, 2, steps / 2,
+ 0x00, 0xfe & rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 3, steps - steps / 2,
+ 0x00, rngmask, 0x00);
+
+ /* branch back to state 1 */
+ /* deceision state w/o data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 4, 0x09, 0x01, rngmask, 0xff);
+
+ break;
+
+ default:
+ dev_err(dev->class_dev, "unsupported combination of channels\n");
+ up(&devpriv->sem);
+ return -EFAULT;
+ }
+
+ /* 0 means that the AD commands are sent */
+ result = usbduxfast_send_cmd(dev, SENDADCOMMANDS);
+ if (result < 0) {
+ up(&devpriv->sem);
+ return result;
+ }
+
+ if ((cmd->start_src == TRIG_NOW) || (cmd->start_src == TRIG_EXT)) {
+ /* enable this acquisition operation */
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxfast_submit_urb(dev);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ /* fixme: unlink here?? */
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = usbduxfast_ai_inttrig;
+ }
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+/*
+ * Mode 0 is used to get a single conversion on demand.
+ */
+static int usbduxfast_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ uint8_t rngmask = range ? (0xff - 0x04) : 0xff;
+ int i, j, n, actual_length;
+ int ret;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_dev,
+ "ai_insn_read not possible, async cmd is running\n");
+ up(&devpriv->sem);
+ return -EBUSY;
+ }
+
+ /* set command for the first channel */
+
+ /* commit data to the FIFO */
+ /* data */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x02, rngmask, 0x00);
+
+ /* do the first part of the delay */
+ usbduxfast_cmd_data(dev, 1, 0x0c, 0x00, 0xfe & rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 2, 0x01, 0x00, 0xfe & rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 3, 0x01, 0x00, 0xfe & rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 4, 0x01, 0x00, 0xfe & rngmask, 0x00);
+
+ /* second part */
+ usbduxfast_cmd_data(dev, 5, 0x0c, 0x00, rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00);
+
+ ret = usbduxfast_send_cmd(dev, SENDADCOMMANDS);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ for (i = 0; i < PACKETS_TO_IGNORE; i++) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, BULKINEP),
+ devpriv->inbuf, SIZEINBUF,
+ &actual_length, 10000);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "insn timeout, no data\n");
+ up(&devpriv->sem);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < insn->n;) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, BULKINEP),
+ devpriv->inbuf, SIZEINBUF,
+ &actual_length, 10000);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "insn data error: %d\n", ret);
+ up(&devpriv->sem);
+ return ret;
+ }
+ n = actual_length / sizeof(uint16_t);
+ if ((n % 16) != 0) {
+ dev_err(dev->class_dev, "insn data packet corrupted\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ for (j = chan; (j < n) && (i < insn->n); j = j + 16) {
+ data[i] = ((uint16_t *) (devpriv->inbuf))[j];
+ i++;
+ }
+ }
+
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbduxfast_attach_common(struct comedi_device *dev)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int ret;
+
+ down(&devpriv->sem);
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
+ s->n_chan = 16;
+ s->len_chanlist = 16;
+ s->insn_read = usbduxfast_ai_insn_read;
+ s->do_cmdtest = usbduxfast_ai_cmdtest;
+ s->do_cmd = usbduxfast_ai_cmd;
+ s->cancel = usbduxfast_ai_cancel;
+ s->maxdata = 0x1000;
+ s->range_table = &range_usbduxfast_ai_range;
+
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static int usbduxfast_upload_firmware(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ uint8_t *buf;
+ unsigned char *tmp;
+ int ret;
+
+ if (!data)
+ return 0;
+
+ if (size > FIRMWARE_MAX_LEN) {
+ dev_err(dev->class_dev, "firmware binary too large for FX2\n");
+ return -ENOMEM;
+ }
+
+ /* we generate a local buffer for the firmware */
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* we need a malloc'ed buffer for usb_control_msg() */
+ tmp = kmalloc(1, GFP_KERNEL);
+ if (!tmp) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* stop the current firmware on the device */
+ *tmp = 1; /* 7f92 to one */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXFASTSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXFASTSUB_CPUCS, 0x0000,
+ tmp, 1,
+ EZTIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "can not stop firmware\n");
+ goto done;
+ }
+
+ /* upload the new firmware to the device */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXFASTSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ 0, 0x0000,
+ buf, size,
+ EZTIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "firmware upload failed\n");
+ goto done;
+ }
+
+ /* start the new firmware on the device */
+ *tmp = 0; /* 7f92 to zero */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXFASTSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXFASTSUB_CPUCS, 0x0000,
+ tmp, 1,
+ EZTIMEOUT);
+ if (ret < 0)
+ dev_err(dev->class_dev, "can not start firmware\n");
+
+done:
+ kfree(tmp);
+ kfree(buf);
+ return ret;
+}
+
+static int usbduxfast_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv;
+ int ret;
+
+ if (usb->speed != USB_SPEED_HIGH) {
+ dev_err(dev->class_dev,
+ "This driver needs USB 2.0 to operate. Aborting...\n");
+ return -ENODEV;
+ }
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+ usb_set_intfdata(intf, devpriv);
+
+ devpriv->duxbuf = kmalloc(SIZEOFDUXBUF, GFP_KERNEL);
+ if (!devpriv->duxbuf)
+ return -ENOMEM;
+
+ ret = usb_set_interface(usb,
+ intf->altsetting->desc.bInterfaceNumber, 1);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "could not switch to alternate setting 1\n");
+ return -ENODEV;
+ }
+
+ devpriv->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!devpriv->urb) {
+ dev_err(dev->class_dev, "Could not alloc. urb\n");
+ return -ENOMEM;
+ }
+
+ devpriv->inbuf = kmalloc(SIZEINBUF, GFP_KERNEL);
+ if (!devpriv->inbuf)
+ return -ENOMEM;
+
+ ret = comedi_load_firmware(dev, &usb->dev, FIRMWARE,
+ usbduxfast_upload_firmware, 0);
+ if (ret)
+ return ret;
+
+ return usbduxfast_attach_common(dev);
+}
+
+static void usbduxfast_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (devpriv->urb) {
+ /* waits until a running transfer is over */
+ usb_kill_urb(devpriv->urb);
+
+ kfree(devpriv->inbuf);
+ devpriv->inbuf = NULL;
+
+ usb_free_urb(devpriv->urb);
+ devpriv->urb = NULL;
+ }
+
+ kfree(devpriv->duxbuf);
+ devpriv->duxbuf = NULL;
+
+ devpriv->ai_cmd_running = 0;
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver usbduxfast_driver = {
+ .driver_name = "usbduxfast",
+ .module = THIS_MODULE,
+ .auto_attach = usbduxfast_auto_attach,
+ .detach = usbduxfast_detach,
+};
+
+static int usbduxfast_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &usbduxfast_driver, 0);
+}
+
+static const struct usb_device_id usbduxfast_usb_table[] = {
+ /* { USB_DEVICE(0x4b4, 0x8613) }, testing */
+ { USB_DEVICE(0x13d8, 0x0010) }, /* real ID */
+ { USB_DEVICE(0x13d8, 0x0011) }, /* real ID */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbduxfast_usb_table);
+
+static struct usb_driver usbduxfast_usb_driver = {
+ .name = "usbduxfast",
+ .probe = usbduxfast_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+ .id_table = usbduxfast_usb_table,
+};
+module_comedi_usb_driver(usbduxfast_driver, usbduxfast_usb_driver);
+
+MODULE_AUTHOR("Bernd Porr, BerndPorr@f2s.com");
+MODULE_DESCRIPTION("USB-DUXfast, BerndPorr@f2s.com");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/usbduxsigma.c b/drivers/staging/comedi/drivers/usbduxsigma.c
new file mode 100644
index 000000000..eaa9add49
--- /dev/null
+++ b/drivers/staging/comedi/drivers/usbduxsigma.c
@@ -0,0 +1,1659 @@
+/*
+ * usbduxsigma.c
+ * Copyright (C) 2011-2014 Bernd Porr, mail@berndporr.me.uk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: usbduxsigma
+ * Description: University of Stirling USB DAQ & INCITE Technology Limited
+ * Devices: [ITL] USB-DUX-SIGMA (usbduxsigma)
+ * Author: Bernd Porr <mail@berndporr.me.uk>
+ * Updated: 10 Oct 2014
+ * Status: stable
+ */
+
+/*
+ * I must give credit here to Chris Baugher who
+ * wrote the driver for AT-MIO-16d. I used some parts of this
+ * driver. I also must give credits to David Brownell
+ * who supported me with the USB development.
+ *
+ * Note: the raw data from the A/D converter is 24 bit big endian
+ * anything else is little endian to/from the dux board
+ *
+ *
+ * Revision history:
+ * 0.1: initial version
+ * 0.2: all basic functions implemented, digital I/O only for one port
+ * 0.3: proper vendor ID and driver name
+ * 0.4: fixed D/A voltage range
+ * 0.5: various bug fixes, health check at startup
+ * 0.6: corrected wrong input range
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/fcntl.h>
+#include <linux/compiler.h>
+#include <asm/unaligned.h>
+
+#include "../comedi_usb.h"
+
+/* timeout for the USB-transfer in ms*/
+#define BULK_TIMEOUT 1000
+
+/* constants for "firmware" upload and download */
+#define FIRMWARE "usbduxsigma_firmware.bin"
+#define FIRMWARE_MAX_LEN 0x4000
+#define USBDUXSUB_FIRMWARE 0xa0
+#define VENDOR_DIR_IN 0xc0
+#define VENDOR_DIR_OUT 0x40
+
+/* internal addresses of the 8051 processor */
+#define USBDUXSUB_CPUCS 0xE600
+
+/* 300Hz max frequ under PWM */
+#define MIN_PWM_PERIOD ((long)(1E9/300))
+
+/* Default PWM frequency */
+#define PWM_DEFAULT_PERIOD ((long)(1E9/100))
+
+/* Number of channels (16 AD and offset)*/
+#define NUMCHANNELS 16
+
+/* Size of one A/D value */
+#define SIZEADIN ((sizeof(uint32_t)))
+
+/*
+ * Size of the async input-buffer IN BYTES, the DIO state is transmitted
+ * as the first byte.
+ */
+#define SIZEINBUF (((NUMCHANNELS+1)*SIZEADIN))
+
+/* 16 bytes. */
+#define SIZEINSNBUF 16
+
+/* Number of DA channels */
+#define NUMOUTCHANNELS 8
+
+/* size of one value for the D/A converter: channel and value */
+#define SIZEDAOUT ((sizeof(uint8_t)+sizeof(uint16_t)))
+
+/*
+ * Size of the output-buffer in bytes
+ * Actually only the first 4 triplets are used but for the
+ * high speed mode we need to pad it to 8 (microframes).
+ */
+#define SIZEOUTBUF ((8*SIZEDAOUT))
+
+/*
+ * Size of the buffer for the dux commands: just now max size is determined
+ * by the analogue out + command byte + panic bytes...
+ */
+#define SIZEOFDUXBUFFER ((8*SIZEDAOUT+2))
+
+/* Number of in-URBs which receive the data: min=2 */
+#define NUMOFINBUFFERSFULL 5
+
+/* Number of out-URBs which send the data: min=2 */
+#define NUMOFOUTBUFFERSFULL 5
+
+/* Number of in-URBs which receive the data: min=5 */
+/* must have more buffers due to buggy USB ctr */
+#define NUMOFINBUFFERSHIGH 10
+
+/* Number of out-URBs which send the data: min=5 */
+/* must have more buffers due to buggy USB ctr */
+#define NUMOFOUTBUFFERSHIGH 10
+
+/* number of retries to get the right dux command */
+#define RETRIES 10
+
+/* bulk transfer commands to usbduxsigma */
+#define USBBUXSIGMA_AD_CMD 0
+#define USBDUXSIGMA_DA_CMD 1
+#define USBDUXSIGMA_DIO_CFG_CMD 2
+#define USBDUXSIGMA_DIO_BITS_CMD 3
+#define USBDUXSIGMA_SINGLE_AD_CMD 4
+#define USBDUXSIGMA_PWM_ON_CMD 7
+#define USBDUXSIGMA_PWM_OFF_CMD 8
+
+static const struct comedi_lrange usbduxsigma_ai_range = {
+ 1, {
+ BIP_RANGE(2.5 * 0x800000 / 0x780000 / 2.0)
+ }
+};
+
+struct usbduxsigma_private {
+ /* actual number of in-buffers */
+ int n_ai_urbs;
+ /* actual number of out-buffers */
+ int n_ao_urbs;
+ /* ISO-transfer handling: buffers */
+ struct urb **ai_urbs;
+ struct urb **ao_urbs;
+ /* pwm-transfer handling */
+ struct urb *pwm_urb;
+ /* PWM period */
+ unsigned int pwm_period;
+ /* PWM internal delay for the GPIF in the FX2 */
+ uint8_t pwm_delay;
+ /* size of the PWM buffer which holds the bit pattern */
+ int pwm_buf_sz;
+ /* input buffer for the ISO-transfer */
+ __be32 *in_buf;
+ /* input buffer for single insn */
+ uint8_t *insn_buf;
+
+ unsigned high_speed:1;
+ unsigned ai_cmd_running:1;
+ unsigned ao_cmd_running:1;
+ unsigned pwm_cmd_running:1;
+
+ /* time between samples in units of the timer */
+ unsigned int ai_timer;
+ unsigned int ao_timer;
+ /* counter between acquisitions */
+ unsigned int ai_counter;
+ unsigned int ao_counter;
+ /* interval in frames/uframes */
+ unsigned int ai_interval;
+ /* commands */
+ uint8_t *dux_commands;
+ struct semaphore sem;
+};
+
+static void usbduxsigma_unlink_urbs(struct urb **urbs, int num_urbs)
+{
+ int i;
+
+ for (i = 0; i < num_urbs; i++)
+ usb_kill_urb(urbs[i]);
+}
+
+static void usbduxsigma_ai_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ai_urbs)
+ usbduxsigma_unlink_urbs(devpriv->ai_urbs, devpriv->n_ai_urbs);
+
+ devpriv->ai_cmd_running = 0;
+}
+
+static int usbduxsigma_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbduxsigma_ai_stop(dev, devpriv->ai_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static void usbduxsigma_ai_handle_urb(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct urb *urb)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ uint32_t val;
+ int ret;
+ int i;
+
+ devpriv->ai_counter--;
+ if (devpriv->ai_counter == 0) {
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ /* get the data from the USB bus and hand it over to comedi */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ /* transfer data, note first byte is the DIO state */
+ val = be32_to_cpu(devpriv->in_buf[i+1]);
+ val &= 0x00ffffff; /* strip status byte */
+ val ^= 0x00800000; /* convert to unsigned */
+
+ if (!comedi_buf_write_samples(s, &val, 1))
+ return;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg)
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ /* if command is still running, resubmit urb */
+ if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
+ urb->dev = comedi_to_usb_dev(dev);
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "urb resubmit failed (%d)\n",
+ ret);
+ if (ret == -EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handler\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+ }
+}
+
+static void usbduxsigma_ai_urb_complete(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+
+ /* exit if not running a command, do not resubmit urb */
+ if (!devpriv->ai_cmd_running)
+ return;
+
+ switch (urb->status) {
+ case 0:
+ /* copy the result in the transfer buffer */
+ memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF);
+ usbduxsigma_ai_handle_urb(dev, s, urb);
+ break;
+
+ case -EILSEQ:
+ /*
+ * error in the ISOchronous data
+ * we don't copy the data into the transfer buffer
+ * and recycle the last data byte
+ */
+ dev_dbg(dev->class_dev, "CRC error in ISO IN stream\n");
+ usbduxsigma_ai_handle_urb(dev, s, urb);
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* happens after an unlink command */
+ async->events |= COMEDI_CB_ERROR;
+ break;
+
+ default:
+ /* a real error */
+ dev_err(dev->class_dev, "non-zero urb status (%d)\n",
+ urb->status);
+ async->events |= COMEDI_CB_ERROR;
+ break;
+ }
+
+ /*
+ * comedi_handle_events() cannot be used in this driver. The (*cancel)
+ * operation would unlink the urb.
+ */
+ if (async->events & COMEDI_CB_CANCEL_MASK)
+ usbduxsigma_ai_stop(dev, 0);
+
+ comedi_event(dev, s);
+}
+
+static void usbduxsigma_ao_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ao_urbs)
+ usbduxsigma_unlink_urbs(devpriv->ao_urbs, devpriv->n_ao_urbs);
+
+ devpriv->ao_cmd_running = 0;
+}
+
+static int usbduxsigma_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbduxsigma_ao_stop(dev, devpriv->ao_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static void usbduxsigma_ao_handle_urb(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct urb *urb)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ uint8_t *datap;
+ int ret;
+ int i;
+
+ devpriv->ao_counter--;
+ if (devpriv->ao_counter == 0) {
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ async->scans_done >= cmd->stop_arg) {
+ async->events |= COMEDI_CB_EOA;
+ return;
+ }
+
+ /* transmit data to the USB bus */
+ datap = urb->transfer_buffer;
+ *datap++ = cmd->chanlist_len;
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned short val;
+
+ if (!comedi_buf_read_samples(s, &val, 1)) {
+ dev_err(dev->class_dev, "buffer underflow\n");
+ async->events |= COMEDI_CB_OVERFLOW;
+ return;
+ }
+
+ *datap++ = val;
+ *datap++ = chan;
+ s->readback[chan] = val;
+ }
+ }
+
+ /* if command is still running, resubmit urb */
+ if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ urb->number_of_packets = 1;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEOUTBUF;
+ urb->iso_frame_desc[0].status = 0;
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "urb resubmit failed (%d)\n",
+ ret);
+ if (ret == -EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handler\n");
+ async->events |= COMEDI_CB_ERROR;
+ }
+ }
+}
+
+static void usbduxsigma_ao_urb_complete(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ struct comedi_async *async = s->async;
+
+ /* exit if not running a command, do not resubmit urb */
+ if (!devpriv->ao_cmd_running)
+ return;
+
+ switch (urb->status) {
+ case 0:
+ usbduxsigma_ao_handle_urb(dev, s, urb);
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* happens after an unlink command */
+ async->events |= COMEDI_CB_ERROR;
+ break;
+
+ default:
+ /* a real error */
+ dev_err(dev->class_dev, "non-zero urb status (%d)\n",
+ urb->status);
+ async->events |= COMEDI_CB_ERROR;
+ break;
+ }
+
+ /*
+ * comedi_handle_events() cannot be used in this driver. The (*cancel)
+ * operation would unlink the urb.
+ */
+ if (async->events & COMEDI_CB_CANCEL_MASK)
+ usbduxsigma_ao_stop(dev, 0);
+
+ comedi_event(dev, s);
+}
+
+static int usbduxsigma_submit_urbs(struct comedi_device *dev,
+ struct urb **urbs, int num_urbs,
+ int input_urb)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb;
+ int ret;
+ int i;
+
+ /* Submit all URBs and start the transfer on the bus */
+ for (i = 0; i < num_urbs; i++) {
+ urb = urbs[i];
+
+ /* in case of a resubmission after an unlink... */
+ if (input_urb)
+ urb->interval = devpriv->ai_interval;
+ urb->context = dev;
+ urb->dev = usb;
+ urb->status = 0;
+ urb->transfer_flags = URB_ISO_ASAP;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int usbduxsigma_chans_to_interval(int num_chan)
+{
+ if (num_chan <= 2)
+ return 2; /* 4kHz */
+ if (num_chan <= 8)
+ return 4; /* 2kHz */
+ return 8; /* 1kHz */
+}
+
+static int usbduxsigma_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int high_speed = devpriv->high_speed;
+ int interval = usbduxsigma_chans_to_interval(cmd->chanlist_len);
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ unsigned int tmp;
+
+ if (high_speed) {
+ /*
+ * In high speed mode microframes are possible.
+ * However, during one microframe we can roughly
+ * sample two channels. Thus, the more channels
+ * are in the channel list the more time we need.
+ */
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ (1000000 / 8 *
+ interval));
+
+ tmp = (cmd->scan_begin_arg / 125000) * 125000;
+ } else {
+ /* full speed */
+ /* 1kHz scans every USB frame */
+ err |= comedi_check_trigger_arg_min(&cmd->
+ scan_begin_arg,
+ 1000000);
+
+ tmp = (cmd->scan_begin_arg / 1000000) * 1000000;
+ }
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, tmp);
+ }
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ if (high_speed) {
+ /*
+ * every 2 channels get a time window of 125us. Thus, if we
+ * sample all 16 channels we need 1ms. If we sample only one
+ * channel we need only 125us
+ */
+ devpriv->ai_interval = interval;
+ devpriv->ai_timer = cmd->scan_begin_arg / (125000 * interval);
+ } else {
+ /* interval always 1ms */
+ devpriv->ai_interval = 1;
+ devpriv->ai_timer = cmd->scan_begin_arg / 1000000;
+ }
+ if (devpriv->ai_timer < 1)
+ err |= -EINVAL;
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+/*
+ * creates the ADC command for the MAX1271
+ * range is the range value from comedi
+ */
+static void create_adc_command(unsigned int chan,
+ uint8_t *muxsg0,
+ uint8_t *muxsg1)
+{
+ if (chan < 8)
+ (*muxsg0) = (*muxsg0) | (1 << chan);
+ else if (chan < 16)
+ (*muxsg1) = (*muxsg1) | (1 << (chan-8));
+}
+
+static int usbbuxsigma_send_cmd(struct comedi_device *dev, int cmd_type)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ int nsent;
+
+ devpriv->dux_commands[0] = cmd_type;
+
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, 1),
+ devpriv->dux_commands, SIZEOFDUXBUFFER,
+ &nsent, BULK_TIMEOUT);
+}
+
+static int usbduxsigma_receive_cmd(struct comedi_device *dev, int command)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ int nrec;
+ int ret;
+ int i;
+
+ for (i = 0; i < RETRIES; i++) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, 8),
+ devpriv->insn_buf, SIZEINSNBUF,
+ &nrec, BULK_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ if (devpriv->insn_buf[0] == command)
+ return 0;
+ }
+ /*
+ * This is only reached if the data has been requested a
+ * couple of times and the command was not received.
+ */
+ return -EFAULT;
+}
+
+static int usbduxsigma_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+ if (!devpriv->ai_cmd_running) {
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ }
+ up(&devpriv->sem);
+
+ return 1;
+}
+
+static int usbduxsigma_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int len = cmd->chanlist_len;
+ uint8_t muxsg0 = 0;
+ uint8_t muxsg1 = 0;
+ uint8_t sysred = 0;
+ int ret;
+ int i;
+
+ down(&devpriv->sem);
+
+ for (i = 0; i < len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ create_adc_command(chan, &muxsg0, &muxsg1);
+ }
+
+ devpriv->dux_commands[1] = len; /* num channels per time step */
+ devpriv->dux_commands[2] = 0x12; /* CONFIG0 */
+ devpriv->dux_commands[3] = 0x03; /* CONFIG1: 23kHz sample, delay 0us */
+ devpriv->dux_commands[4] = 0x00; /* CONFIG3: diff. channels off */
+ devpriv->dux_commands[5] = muxsg0;
+ devpriv->dux_commands[6] = muxsg1;
+ devpriv->dux_commands[7] = sysred;
+
+ ret = usbbuxsigma_send_cmd(dev, USBBUXSIGMA_AD_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = usbduxsigma_ai_inttrig;
+ }
+
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static int usbduxsigma_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint8_t muxsg0 = 0;
+ uint8_t muxsg1 = 0;
+ uint8_t sysred = 0;
+ int ret;
+ int i;
+
+ down(&devpriv->sem);
+ if (devpriv->ai_cmd_running) {
+ up(&devpriv->sem);
+ return -EBUSY;
+ }
+
+ create_adc_command(chan, &muxsg0, &muxsg1);
+
+ /* Mode 0 is used to get a single conversion on demand */
+ devpriv->dux_commands[1] = 0x16; /* CONFIG0: chopper on */
+ devpriv->dux_commands[2] = 0x80; /* CONFIG1: 2kHz sampling rate */
+ devpriv->dux_commands[3] = 0x00; /* CONFIG3: diff. channels off */
+ devpriv->dux_commands[4] = muxsg0;
+ devpriv->dux_commands[5] = muxsg1;
+ devpriv->dux_commands[6] = sysred;
+
+ /* adc commands */
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ uint32_t val;
+
+ ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ /* 32 bits big endian from the A/D converter */
+ val = be32_to_cpu(get_unaligned((__be32
+ *)(devpriv->insn_buf + 1)));
+ val &= 0x00ffffff; /* strip status byte */
+ val ^= 0x00800000; /* convert to unsigned */
+
+ data[i] = val;
+ }
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbduxsigma_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+ ret = comedi_readback_insn_read(dev, s, insn, data);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int usbduxsigma_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ int i;
+
+ down(&devpriv->sem);
+ if (devpriv->ao_cmd_running) {
+ up(&devpriv->sem);
+ return -EBUSY;
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ devpriv->dux_commands[1] = 1; /* num channels */
+ devpriv->dux_commands[2] = data[i]; /* value */
+ devpriv->dux_commands[3] = chan; /* channel number */
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_DA_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->readback[chan] = data[i];
+ }
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbduxsigma_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+ if (!devpriv->ao_cmd_running) {
+ devpriv->ao_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ }
+ up(&devpriv->sem);
+
+ return 1;
+}
+
+static int usbduxsigma_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int err = 0;
+ int high_speed;
+ unsigned int flags;
+
+ /* high speed conversions are not used yet */
+ high_speed = 0; /* (devpriv->high_speed) */
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+
+ if (high_speed) {
+ /*
+ * start immediately a new scan
+ * the sampling rate is set by the coversion rate
+ */
+ flags = TRIG_FOLLOW;
+ } else {
+ /* start a new scan (output at once) with a timer */
+ flags = TRIG_TIMER;
+ }
+ err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags);
+
+ err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err) {
+ up(&devpriv->sem);
+ return 1;
+ }
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= comedi_check_trigger_is_unique(cmd->start_src);
+ err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 1000000);
+ }
+
+ /* not used now, is for later use */
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 125000);
+
+ err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* we count in timer steps */
+ if (high_speed) {
+ /* timing of the conversion itself: every 125 us */
+ devpriv->ao_timer = cmd->convert_arg / 125000;
+ } else {
+ /*
+ * timing of the scan: every 1ms
+ * we get all channels at once
+ */
+ devpriv->ao_timer = cmd->scan_begin_arg / 1000000;
+ }
+ if (devpriv->ao_timer < 1)
+ err |= -EINVAL;
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int usbduxsigma_ao_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ down(&devpriv->sem);
+
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ao_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = usbduxsigma_ao_inttrig;
+ }
+
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static int usbduxsigma_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * We don't tell the firmware here as it would take 8 frames
+ * to submit the information. We do it in the (*insn_bits).
+ */
+ return insn->n;
+}
+
+static int usbduxsigma_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+
+ comedi_dio_update_state(s, data);
+
+ /* Always update the hardware. See the (*insn_config). */
+ devpriv->dux_commands[1] = s->io_bits & 0xff;
+ devpriv->dux_commands[4] = s->state & 0xff;
+ devpriv->dux_commands[2] = (s->io_bits >> 8) & 0xff;
+ devpriv->dux_commands[5] = (s->state >> 8) & 0xff;
+ devpriv->dux_commands[3] = (s->io_bits >> 16) & 0xff;
+ devpriv->dux_commands[6] = (s->state >> 16) & 0xff;
+
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_DIO_BITS_CMD);
+ if (ret < 0)
+ goto done;
+ ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_DIO_BITS_CMD);
+ if (ret < 0)
+ goto done;
+
+ s->state = devpriv->insn_buf[1] |
+ (devpriv->insn_buf[2] << 8) |
+ (devpriv->insn_buf[3] << 16);
+
+ data[1] = s->state;
+ ret = insn->n;
+
+done:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void usbduxsigma_pwm_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ if (do_unlink) {
+ if (devpriv->pwm_urb)
+ usb_kill_urb(devpriv->pwm_urb);
+ }
+
+ devpriv->pwm_cmd_running = 0;
+}
+
+static int usbduxsigma_pwm_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ /* unlink only if it is really running */
+ usbduxsigma_pwm_stop(dev, devpriv->pwm_cmd_running);
+
+ return usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_OFF_CMD);
+}
+
+static void usbduxsigma_pwm_urb_complete(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbduxsigma_private *devpriv = dev->private;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* happens after an unlink command */
+ if (devpriv->pwm_cmd_running)
+ usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */
+ return;
+
+ default:
+ /* a real error */
+ if (devpriv->pwm_cmd_running) {
+ dev_err(dev->class_dev, "non-zero urb status (%d)\n",
+ urb->status);
+ usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */
+ }
+ return;
+ }
+
+ if (!devpriv->pwm_cmd_running)
+ return;
+
+ urb->transfer_buffer_length = devpriv->pwm_buf_sz;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "urb resubmit failed (%d)\n", ret);
+ if (ret == -EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handler\n");
+ usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */
+ }
+}
+
+static int usbduxsigma_submit_pwm_urb(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb = devpriv->pwm_urb;
+
+ /* in case of a resubmission after an unlink... */
+ usb_fill_bulk_urb(urb, usb, usb_sndbulkpipe(usb, 4),
+ urb->transfer_buffer, devpriv->pwm_buf_sz,
+ usbduxsigma_pwm_urb_complete, dev);
+
+ return usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int usbduxsigma_pwm_period(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int period)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int fx2delay = 255;
+
+ if (period < MIN_PWM_PERIOD)
+ return -EAGAIN;
+
+ fx2delay = (period / (6 * 512 * 1000 / 33)) - 6;
+ if (fx2delay > 255)
+ return -EAGAIN;
+
+ devpriv->pwm_delay = fx2delay;
+ devpriv->pwm_period = period;
+ return 0;
+}
+
+static int usbduxsigma_pwm_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int ret;
+
+ if (devpriv->pwm_cmd_running)
+ return 0;
+
+ devpriv->dux_commands[1] = devpriv->pwm_delay;
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_ON_CMD);
+ if (ret < 0)
+ return ret;
+
+ memset(devpriv->pwm_urb->transfer_buffer, 0, devpriv->pwm_buf_sz);
+
+ devpriv->pwm_cmd_running = 1;
+ ret = usbduxsigma_submit_pwm_urb(dev);
+ if (ret < 0) {
+ devpriv->pwm_cmd_running = 0;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void usbduxsigma_pwm_pattern(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int value,
+ unsigned int sign)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ char pwm_mask = (1 << chan); /* DIO bit for the PWM data */
+ char sgn_mask = (16 << chan); /* DIO bit for the sign */
+ char *buf = (char *)(devpriv->pwm_urb->transfer_buffer);
+ int szbuf = devpriv->pwm_buf_sz;
+ int i;
+
+ for (i = 0; i < szbuf; i++) {
+ char c = *buf;
+
+ c &= ~pwm_mask;
+ if (i < value)
+ c |= pwm_mask;
+ if (!sign)
+ c &= ~sgn_mask;
+ else
+ c |= sgn_mask;
+ *buf++ = c;
+ }
+}
+
+static int usbduxsigma_pwm_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * It doesn't make sense to support more than one value here
+ * because it would just overwrite the PWM buffer.
+ */
+ if (insn->n != 1)
+ return -EINVAL;
+
+ /*
+ * The sign is set via a special INSN only, this gives us 8 bits
+ * for normal operation, sign is 0 by default.
+ */
+ usbduxsigma_pwm_pattern(dev, s, chan, data[0], 0);
+
+ return insn->n;
+}
+
+static int usbduxsigma_pwm_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ /*
+ * if not zero the PWM is limited to a certain time which is
+ * not supported here
+ */
+ if (data[1] != 0)
+ return -EINVAL;
+ return usbduxsigma_pwm_start(dev, s);
+ case INSN_CONFIG_DISARM:
+ return usbduxsigma_pwm_cancel(dev, s);
+ case INSN_CONFIG_GET_PWM_STATUS:
+ data[1] = devpriv->pwm_cmd_running;
+ return 0;
+ case INSN_CONFIG_PWM_SET_PERIOD:
+ return usbduxsigma_pwm_period(dev, s, data[1]);
+ case INSN_CONFIG_PWM_GET_PERIOD:
+ data[1] = devpriv->pwm_period;
+ return 0;
+ case INSN_CONFIG_PWM_SET_H_BRIDGE:
+ /*
+ * data[1] = value
+ * data[2] = sign (for a relay)
+ */
+ usbduxsigma_pwm_pattern(dev, s, chan, data[1], (data[2] != 0));
+ return 0;
+ case INSN_CONFIG_PWM_GET_H_BRIDGE:
+ /* values are not kept in this driver, nothing to return */
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+static int usbduxsigma_getstatusinfo(struct comedi_device *dev, int chan)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ uint8_t sysred;
+ uint32_t val;
+ int ret;
+
+ switch (chan) {
+ default:
+ case 0:
+ sysred = 0; /* ADC zero */
+ break;
+ case 1:
+ sysred = 1; /* ADC offset */
+ break;
+ case 2:
+ sysred = 4; /* VCC */
+ break;
+ case 3:
+ sysred = 8; /* temperature */
+ break;
+ case 4:
+ sysred = 16; /* gain */
+ break;
+ case 5:
+ sysred = 32; /* ref */
+ break;
+ }
+
+ devpriv->dux_commands[1] = 0x12; /* CONFIG0 */
+ devpriv->dux_commands[2] = 0x80; /* CONFIG1: 2kHz sampling rate */
+ devpriv->dux_commands[3] = 0x00; /* CONFIG3: diff. channels off */
+ devpriv->dux_commands[4] = 0;
+ devpriv->dux_commands[5] = 0;
+ devpriv->dux_commands[6] = sysred;
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0)
+ return ret;
+
+ ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0)
+ return ret;
+
+ /* 32 bits big endian from the A/D converter */
+ val = be32_to_cpu(get_unaligned((__be32 *)(devpriv->insn_buf + 1)));
+ val &= 0x00ffffff; /* strip status byte */
+ val ^= 0x00800000; /* convert to unsigned */
+
+ return (int)val;
+}
+
+static int usbduxsigma_firmware_upload(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ uint8_t *buf;
+ uint8_t *tmp;
+ int ret;
+
+ if (!data)
+ return 0;
+
+ if (size > FIRMWARE_MAX_LEN) {
+ dev_err(dev->class_dev, "firmware binary too large for FX2\n");
+ return -ENOMEM;
+ }
+
+ /* we generate a local buffer for the firmware */
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* we need a malloc'ed buffer for usb_control_msg() */
+ tmp = kmalloc(1, GFP_KERNEL);
+ if (!tmp) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* stop the current firmware on the device */
+ *tmp = 1; /* 7f92 to one */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXSUB_CPUCS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "can not stop firmware\n");
+ goto done;
+ }
+
+ /* upload the new firmware to the device */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ 0, 0x0000,
+ buf, size,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "firmware upload failed\n");
+ goto done;
+ }
+
+ /* start the new firmware on the device */
+ *tmp = 0; /* 7f92 to zero */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXSUB_CPUCS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0)
+ dev_err(dev->class_dev, "can not start firmware\n");
+
+done:
+ kfree(tmp);
+ kfree(buf);
+ return ret;
+}
+
+static int usbduxsigma_alloc_usb_buffers(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ devpriv->dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL);
+ devpriv->in_buf = kzalloc(SIZEINBUF, GFP_KERNEL);
+ devpriv->insn_buf = kzalloc(SIZEINSNBUF, GFP_KERNEL);
+ devpriv->ai_urbs = kcalloc(devpriv->n_ai_urbs, sizeof(urb), GFP_KERNEL);
+ devpriv->ao_urbs = kcalloc(devpriv->n_ao_urbs, sizeof(urb), GFP_KERNEL);
+ if (!devpriv->dux_commands || !devpriv->in_buf || !devpriv->insn_buf ||
+ !devpriv->ai_urbs || !devpriv->ao_urbs)
+ return -ENOMEM;
+
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ai_urbs[i] = urb;
+ urb->dev = usb;
+ /* will be filled later with a pointer to the comedi-device */
+ /* and ONLY then the urb should be submitted */
+ urb->context = NULL;
+ urb->pipe = usb_rcvisocpipe(usb, 6);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEINBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ urb->complete = usbduxsigma_ai_urb_complete;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEINBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEINBUF;
+ }
+
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ao_urbs[i] = urb;
+ urb->dev = usb;
+ /* will be filled later with a pointer to the comedi-device */
+ /* and ONLY then the urb should be submitted */
+ urb->context = NULL;
+ urb->pipe = usb_sndisocpipe(usb, 2);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEOUTBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ urb->complete = usbduxsigma_ao_urb_complete;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEOUTBUF;
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ }
+
+ if (devpriv->pwm_buf_sz) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->pwm_urb = urb;
+
+ urb->transfer_buffer = kzalloc(devpriv->pwm_buf_sz,
+ GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void usbduxsigma_free_usb_buffers(struct comedi_device *dev)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ urb = devpriv->pwm_urb;
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ if (devpriv->ao_urbs) {
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ urb = devpriv->ao_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ao_urbs);
+ }
+ if (devpriv->ai_urbs) {
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ urb = devpriv->ai_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ai_urbs);
+ }
+ kfree(devpriv->insn_buf);
+ kfree(devpriv->in_buf);
+ kfree(devpriv->dux_commands);
+}
+
+static int usbduxsigma_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv;
+ struct comedi_subdevice *s;
+ int offset;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+
+ usb_set_intfdata(intf, devpriv);
+
+ devpriv->high_speed = (usb->speed == USB_SPEED_HIGH);
+ if (devpriv->high_speed) {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSHIGH;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSHIGH;
+ devpriv->pwm_buf_sz = 512;
+ } else {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSFULL;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSFULL;
+ }
+
+ ret = usbduxsigma_alloc_usb_buffers(dev);
+ if (ret)
+ return ret;
+
+ /* setting to alternate setting 3: enabling iso ep and bulk ep. */
+ ret = usb_set_interface(usb, intf->altsetting->desc.bInterfaceNumber,
+ 3);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "could not set alternate setting 3 in high speed\n");
+ return ret;
+ }
+
+ ret = comedi_load_firmware(dev, &usb->dev, FIRMWARE,
+ usbduxsigma_firmware_upload, 0);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 4 : 3);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ | SDF_LSAMPL;
+ s->n_chan = NUMCHANNELS;
+ s->len_chanlist = NUMCHANNELS;
+ s->maxdata = 0x00ffffff;
+ s->range_table = &usbduxsigma_ai_range;
+ s->insn_read = usbduxsigma_ai_insn_read;
+ s->do_cmdtest = usbduxsigma_ai_cmdtest;
+ s->do_cmd = usbduxsigma_ai_cmd;
+ s->cancel = usbduxsigma_ai_cancel;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ dev->write_subdev = s;
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
+ s->n_chan = 4;
+ s->len_chanlist = s->n_chan;
+ s->maxdata = 0x00ff;
+ s->range_table = &range_unipolar2_5;
+ s->insn_write = usbduxsigma_ao_insn_write;
+ s->insn_read = usbduxsigma_ao_insn_read;
+ s->do_cmdtest = usbduxsigma_ao_cmdtest;
+ s->do_cmd = usbduxsigma_ao_cmd;
+ s->cancel = usbduxsigma_ao_cancel;
+
+ ret = comedi_alloc_subdev_readback(s);
+ if (ret)
+ return ret;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = usbduxsigma_dio_insn_bits;
+ s->insn_config = usbduxsigma_dio_insn_config;
+
+ if (devpriv->high_speed) {
+ /* Timer / pwm subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE;
+ s->n_chan = 8;
+ s->maxdata = devpriv->pwm_buf_sz;
+ s->insn_write = usbduxsigma_pwm_write;
+ s->insn_config = usbduxsigma_pwm_config;
+
+ usbduxsigma_pwm_period(dev, s, PWM_DEFAULT_PERIOD);
+ }
+
+ offset = usbduxsigma_getstatusinfo(dev, 0);
+ if (offset < 0) {
+ dev_err(dev->class_dev,
+ "Communication to USBDUXSIGMA failed! Check firmware and cabling.\n");
+ return offset;
+ }
+
+ dev_info(dev->class_dev, "ADC_zero = %x\n", offset);
+
+ return 0;
+}
+
+static void usbduxsigma_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ /* force unlink all urbs */
+ usbduxsigma_ai_stop(dev, 1);
+ usbduxsigma_ao_stop(dev, 1);
+ usbduxsigma_pwm_stop(dev, 1);
+
+ usbduxsigma_free_usb_buffers(dev);
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver usbduxsigma_driver = {
+ .driver_name = "usbduxsigma",
+ .module = THIS_MODULE,
+ .auto_attach = usbduxsigma_auto_attach,
+ .detach = usbduxsigma_detach,
+};
+
+static int usbduxsigma_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &usbduxsigma_driver, 0);
+}
+
+static const struct usb_device_id usbduxsigma_usb_table[] = {
+ { USB_DEVICE(0x13d8, 0x0020) },
+ { USB_DEVICE(0x13d8, 0x0021) },
+ { USB_DEVICE(0x13d8, 0x0022) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbduxsigma_usb_table);
+
+static struct usb_driver usbduxsigma_usb_driver = {
+ .name = "usbduxsigma",
+ .probe = usbduxsigma_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+ .id_table = usbduxsigma_usb_table,
+};
+module_comedi_usb_driver(usbduxsigma_driver, usbduxsigma_usb_driver);
+
+MODULE_AUTHOR("Bernd Porr, BerndPorr@f2s.com");
+MODULE_DESCRIPTION("Stirling/ITL USB-DUX SIGMA -- Bernd.Porr@f2s.com");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/vmk80xx.c b/drivers/staging/comedi/drivers/vmk80xx.c
new file mode 100644
index 000000000..a0906685e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/vmk80xx.c
@@ -0,0 +1,885 @@
+/*
+ comedi/drivers/vmk80xx.c
+ Velleman USB Board Low-Level Driver
+
+ Copyright (C) 2009 Manuel Gebele <forensixs@gmx.de>, Germany
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+/*
+ * Driver: vmk80xx
+ * Description: Velleman USB Board Low-Level Driver
+ * Devices: [Velleman] K8055 (K8055/VM110), K8061 (K8061/VM140),
+ * VM110 (K8055/VM110), VM140 (K8061/VM140)
+ * Author: Manuel Gebele <forensixs@gmx.de>
+ * Updated: Sun, 10 May 2009 11:14:59 +0200
+ * Status: works
+ *
+ * Supports:
+ * - analog input
+ * - analog output
+ * - digital input
+ * - digital output
+ * - counter
+ * - pwm
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+
+#include "../comedi_usb.h"
+
+enum {
+ DEVICE_VMK8055,
+ DEVICE_VMK8061
+};
+
+#define VMK8055_DI_REG 0x00
+#define VMK8055_DO_REG 0x01
+#define VMK8055_AO1_REG 0x02
+#define VMK8055_AO2_REG 0x03
+#define VMK8055_AI1_REG 0x02
+#define VMK8055_AI2_REG 0x03
+#define VMK8055_CNT1_REG 0x04
+#define VMK8055_CNT2_REG 0x06
+
+#define VMK8061_CH_REG 0x01
+#define VMK8061_DI_REG 0x01
+#define VMK8061_DO_REG 0x01
+#define VMK8061_PWM_REG1 0x01
+#define VMK8061_PWM_REG2 0x02
+#define VMK8061_CNT_REG 0x02
+#define VMK8061_AO_REG 0x02
+#define VMK8061_AI_REG1 0x02
+#define VMK8061_AI_REG2 0x03
+
+#define VMK8055_CMD_RST 0x00
+#define VMK8055_CMD_DEB1_TIME 0x01
+#define VMK8055_CMD_DEB2_TIME 0x02
+#define VMK8055_CMD_RST_CNT1 0x03
+#define VMK8055_CMD_RST_CNT2 0x04
+#define VMK8055_CMD_WRT_AD 0x05
+
+#define VMK8061_CMD_RD_AI 0x00
+#define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */
+#define VMK8061_CMD_SET_AO 0x02
+#define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */
+#define VMK8061_CMD_OUT_PWM 0x04
+#define VMK8061_CMD_RD_DI 0x05
+#define VMK8061_CMD_DO 0x06 /* !non-active! */
+#define VMK8061_CMD_CLR_DO 0x07
+#define VMK8061_CMD_SET_DO 0x08
+#define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */
+#define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */
+#define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */
+#define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */
+#define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */
+#define VMK8061_CMD_RD_DO 0x0e
+#define VMK8061_CMD_RD_AO 0x0f
+#define VMK8061_CMD_RD_PWM 0x10
+
+#define IC3_VERSION (1 << 0)
+#define IC6_VERSION (1 << 1)
+
+enum vmk80xx_model {
+ VMK8055_MODEL,
+ VMK8061_MODEL
+};
+
+static const struct comedi_lrange vmk8061_range = {
+ 2, {
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+struct vmk80xx_board {
+ const char *name;
+ enum vmk80xx_model model;
+ const struct comedi_lrange *range;
+ int ai_nchans;
+ unsigned int ai_maxdata;
+ int ao_nchans;
+ int di_nchans;
+ unsigned int cnt_maxdata;
+ int pwm_nchans;
+ unsigned int pwm_maxdata;
+};
+
+static const struct vmk80xx_board vmk80xx_boardinfo[] = {
+ [DEVICE_VMK8055] = {
+ .name = "K8055 (VM110)",
+ .model = VMK8055_MODEL,
+ .range = &range_unipolar5,
+ .ai_nchans = 2,
+ .ai_maxdata = 0x00ff,
+ .ao_nchans = 2,
+ .di_nchans = 6,
+ .cnt_maxdata = 0xffff,
+ },
+ [DEVICE_VMK8061] = {
+ .name = "K8061 (VM140)",
+ .model = VMK8061_MODEL,
+ .range = &vmk8061_range,
+ .ai_nchans = 8,
+ .ai_maxdata = 0x03ff,
+ .ao_nchans = 8,
+ .di_nchans = 8,
+ .cnt_maxdata = 0, /* unknown, device is not writeable */
+ .pwm_nchans = 1,
+ .pwm_maxdata = 0x03ff,
+ },
+};
+
+struct vmk80xx_private {
+ struct usb_endpoint_descriptor *ep_rx;
+ struct usb_endpoint_descriptor *ep_tx;
+ struct semaphore limit_sem;
+ unsigned char *usb_rx_buf;
+ unsigned char *usb_tx_buf;
+ enum vmk80xx_model model;
+};
+
+static void vmk80xx_do_bulk_msg(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ __u8 tx_addr;
+ __u8 rx_addr;
+ unsigned int tx_pipe;
+ unsigned int rx_pipe;
+ size_t size;
+
+ tx_addr = devpriv->ep_tx->bEndpointAddress;
+ rx_addr = devpriv->ep_rx->bEndpointAddress;
+ tx_pipe = usb_sndbulkpipe(usb, tx_addr);
+ rx_pipe = usb_rcvbulkpipe(usb, rx_addr);
+
+ /*
+ * The max packet size attributes of the K8061
+ * input/output endpoints are identical
+ */
+ size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+
+ usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf,
+ size, NULL, devpriv->ep_tx->bInterval);
+ usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, size, NULL, HZ * 10);
+}
+
+static int vmk80xx_read_packet(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usb_endpoint_descriptor *ep;
+ unsigned int pipe;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ vmk80xx_do_bulk_msg(dev);
+ return 0;
+ }
+
+ ep = devpriv->ep_rx;
+ pipe = usb_rcvintpipe(usb, ep->bEndpointAddress);
+ return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf,
+ le16_to_cpu(ep->wMaxPacketSize), NULL,
+ HZ * 10);
+}
+
+static int vmk80xx_write_packet(struct comedi_device *dev, int cmd)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usb_endpoint_descriptor *ep;
+ unsigned int pipe;
+
+ devpriv->usb_tx_buf[0] = cmd;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ vmk80xx_do_bulk_msg(dev);
+ return 0;
+ }
+
+ ep = devpriv->ep_tx;
+ pipe = usb_sndintpipe(usb, ep->bEndpointAddress);
+ return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf,
+ le16_to_cpu(ep->wMaxPacketSize), NULL,
+ HZ * 10);
+}
+
+static int vmk80xx_reset_device(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ size_t size;
+ int retval;
+
+ size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+ memset(devpriv->usb_tx_buf, 0, size);
+ retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST);
+ if (retval)
+ return retval;
+ /* set outputs to known state as we cannot read them */
+ return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD);
+}
+
+static int vmk80xx_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int reg[2];
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ switch (devpriv->model) {
+ case VMK8055_MODEL:
+ if (!chan)
+ reg[0] = VMK8055_AI1_REG;
+ else
+ reg[0] = VMK8055_AI2_REG;
+ break;
+ case VMK8061_MODEL:
+ default:
+ reg[0] = VMK8061_AI_REG1;
+ reg[1] = VMK8061_AI_REG2;
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI;
+ devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
+ break;
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ if (devpriv->model == VMK8055_MODEL) {
+ data[n] = devpriv->usb_rx_buf[reg[0]];
+ continue;
+ }
+
+ /* VMK8061_MODEL */
+ data[n] = devpriv->usb_rx_buf[reg[0]] + 256 *
+ devpriv->usb_rx_buf[reg[1]];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int cmd;
+ int reg;
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ switch (devpriv->model) {
+ case VMK8055_MODEL:
+ cmd = VMK8055_CMD_WRT_AD;
+ if (!chan)
+ reg = VMK8055_AO1_REG;
+ else
+ reg = VMK8055_AO2_REG;
+ break;
+ default: /* NOTE: avoid compiler warnings */
+ cmd = VMK8061_CMD_SET_AO;
+ reg = VMK8061_AO_REG;
+ devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
+ break;
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ devpriv->usb_tx_buf[reg] = data[n];
+
+ if (vmk80xx_write_packet(dev, cmd))
+ break;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int reg;
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ reg = VMK8061_AO_REG - 1;
+
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO;
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ data[n] = devpriv->usb_rx_buf[reg + chan];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *rx_buf;
+ int reg;
+ int retval;
+
+ down(&devpriv->limit_sem);
+
+ rx_buf = devpriv->usb_rx_buf;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ reg = VMK8061_DI_REG;
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI;
+ } else {
+ reg = VMK8055_DI_REG;
+ }
+
+ retval = vmk80xx_read_packet(dev);
+
+ if (!retval) {
+ if (devpriv->model == VMK8055_MODEL)
+ data[1] = (((rx_buf[reg] >> 4) & 0x03) |
+ ((rx_buf[reg] << 2) & 0x04) |
+ ((rx_buf[reg] >> 3) & 0x18));
+ else
+ data[1] = rx_buf[reg];
+
+ retval = 2;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return retval;
+}
+
+static int vmk80xx_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *rx_buf = devpriv->usb_rx_buf;
+ unsigned char *tx_buf = devpriv->usb_tx_buf;
+ int reg, cmd;
+ int ret = 0;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ reg = VMK8061_DO_REG;
+ cmd = VMK8061_CMD_DO;
+ } else { /* VMK8055_MODEL */
+ reg = VMK8055_DO_REG;
+ cmd = VMK8055_CMD_WRT_AD;
+ }
+
+ down(&devpriv->limit_sem);
+
+ if (comedi_dio_update_state(s, data)) {
+ tx_buf[reg] = s->state;
+ ret = vmk80xx_write_packet(dev, cmd);
+ if (ret)
+ goto out;
+ }
+
+ if (devpriv->model == VMK8061_MODEL) {
+ tx_buf[0] = VMK8061_CMD_RD_DO;
+ ret = vmk80xx_read_packet(dev);
+ if (ret)
+ goto out;
+ data[1] = rx_buf[reg];
+ } else {
+ data[1] = s->state;
+ }
+
+out:
+ up(&devpriv->limit_sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int vmk80xx_cnt_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int reg[2];
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ switch (devpriv->model) {
+ case VMK8055_MODEL:
+ if (!chan)
+ reg[0] = VMK8055_CNT1_REG;
+ else
+ reg[0] = VMK8055_CNT2_REG;
+ break;
+ case VMK8061_MODEL:
+ default:
+ reg[0] = VMK8061_CNT_REG;
+ reg[1] = VMK8061_CNT_REG;
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT;
+ break;
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ if (devpriv->model == VMK8055_MODEL)
+ data[n] = devpriv->usb_rx_buf[reg[0]];
+ else /* VMK8061_MODEL */
+ data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1]
+ + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_cnt_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int cmd;
+ int reg;
+ int ret;
+
+ down(&devpriv->limit_sem);
+ switch (data[0]) {
+ case INSN_CONFIG_RESET:
+ if (devpriv->model == VMK8055_MODEL) {
+ if (!chan) {
+ cmd = VMK8055_CMD_RST_CNT1;
+ reg = VMK8055_CNT1_REG;
+ } else {
+ cmd = VMK8055_CMD_RST_CNT2;
+ reg = VMK8055_CNT2_REG;
+ }
+ devpriv->usb_tx_buf[reg] = 0x00;
+ } else {
+ cmd = VMK8061_CMD_RST_CNT;
+ }
+ ret = vmk80xx_write_packet(dev, cmd);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ up(&devpriv->limit_sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int vmk80xx_cnt_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned long debtime;
+ unsigned long val;
+ int chan;
+ int cmd;
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ if (!chan)
+ cmd = VMK8055_CMD_DEB1_TIME;
+ else
+ cmd = VMK8055_CMD_DEB2_TIME;
+
+ for (n = 0; n < insn->n; n++) {
+ debtime = data[n];
+ if (debtime == 0)
+ debtime = 1;
+
+ /* TODO: Prevent overflows */
+ if (debtime > 7450)
+ debtime = 7450;
+
+ val = int_sqrt(debtime * 1000 / 115);
+ if (((val + 1) * val) < debtime * 1000 / 115)
+ val += 1;
+
+ devpriv->usb_tx_buf[6 + chan] = val;
+
+ if (vmk80xx_write_packet(dev, cmd))
+ break;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_pwm_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *tx_buf;
+ unsigned char *rx_buf;
+ int reg[2];
+ int n;
+
+ down(&devpriv->limit_sem);
+
+ tx_buf = devpriv->usb_tx_buf;
+ rx_buf = devpriv->usb_rx_buf;
+
+ reg[0] = VMK8061_PWM_REG1;
+ reg[1] = VMK8061_PWM_REG2;
+
+ tx_buf[0] = VMK8061_CMD_RD_PWM;
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_pwm_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *tx_buf;
+ int reg[2];
+ int cmd;
+ int n;
+
+ down(&devpriv->limit_sem);
+
+ tx_buf = devpriv->usb_tx_buf;
+
+ reg[0] = VMK8061_PWM_REG1;
+ reg[1] = VMK8061_PWM_REG2;
+
+ cmd = VMK8061_CMD_OUT_PWM;
+
+ /*
+ * The followin piece of code was translated from the inline
+ * assembler code in the DLL source code.
+ *
+ * asm
+ * mov eax, k ; k is the value (data[n])
+ * and al, 03h ; al are the lower 8 bits of eax
+ * mov lo, al ; lo is the low part (tx_buf[reg[0]])
+ * mov eax, k
+ * shr eax, 2 ; right shift eax register by 2
+ * mov hi, al ; hi is the high part (tx_buf[reg[1]])
+ * end;
+ */
+ for (n = 0; n < insn->n; n++) {
+ tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03);
+ tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff;
+
+ if (vmk80xx_write_packet(dev, cmd))
+ break;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_find_usb_endpoints(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_host_interface *iface_desc = intf->cur_altsetting;
+ struct usb_endpoint_descriptor *ep_desc;
+ int i;
+
+ if (iface_desc->desc.bNumEndpoints != 2)
+ return -ENODEV;
+
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+ ep_desc = &iface_desc->endpoint[i].desc;
+
+ if (usb_endpoint_is_int_in(ep_desc) ||
+ usb_endpoint_is_bulk_in(ep_desc)) {
+ if (!devpriv->ep_rx)
+ devpriv->ep_rx = ep_desc;
+ continue;
+ }
+
+ if (usb_endpoint_is_int_out(ep_desc) ||
+ usb_endpoint_is_bulk_out(ep_desc)) {
+ if (!devpriv->ep_tx)
+ devpriv->ep_tx = ep_desc;
+ continue;
+ }
+ }
+
+ if (!devpriv->ep_rx || !devpriv->ep_tx)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ size_t size;
+
+ size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize);
+ devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
+ if (!devpriv->usb_rx_buf)
+ return -ENOMEM;
+
+ size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+ devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
+ if (!devpriv->usb_tx_buf) {
+ kfree(devpriv->usb_rx_buf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int vmk80xx_init_subdevices(struct comedi_device *dev)
+{
+ const struct vmk80xx_board *boardinfo = dev->board_ptr;
+ struct vmk80xx_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int n_subd;
+ int ret;
+
+ down(&devpriv->limit_sem);
+
+ if (devpriv->model == VMK8055_MODEL)
+ n_subd = 5;
+ else
+ n_subd = 6;
+ ret = comedi_alloc_subdevices(dev, n_subd);
+ if (ret) {
+ up(&devpriv->limit_sem);
+ return ret;
+ }
+
+ /* Analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = boardinfo->ai_nchans;
+ s->maxdata = boardinfo->ai_maxdata;
+ s->range_table = boardinfo->range;
+ s->insn_read = vmk80xx_ai_insn_read;
+
+ /* Analog output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = boardinfo->ao_nchans;
+ s->maxdata = 0x00ff;
+ s->range_table = boardinfo->range;
+ s->insn_write = vmk80xx_ao_insn_write;
+ if (devpriv->model == VMK8061_MODEL) {
+ s->subdev_flags |= SDF_READABLE;
+ s->insn_read = vmk80xx_ao_insn_read;
+ }
+
+ /* Digital input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = boardinfo->di_nchans;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = vmk80xx_di_insn_bits;
+
+ /* Digital output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = vmk80xx_do_insn_bits;
+
+ /* Counter subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 2;
+ s->maxdata = boardinfo->cnt_maxdata;
+ s->insn_read = vmk80xx_cnt_insn_read;
+ s->insn_config = vmk80xx_cnt_insn_config;
+ if (devpriv->model == VMK8055_MODEL) {
+ s->subdev_flags |= SDF_WRITABLE;
+ s->insn_write = vmk80xx_cnt_insn_write;
+ }
+
+ /* PWM subdevice */
+ if (devpriv->model == VMK8061_MODEL) {
+ s = &dev->subdevices[5];
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = boardinfo->pwm_nchans;
+ s->maxdata = boardinfo->pwm_maxdata;
+ s->insn_read = vmk80xx_pwm_insn_read;
+ s->insn_write = vmk80xx_pwm_insn_write;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return 0;
+}
+
+static int vmk80xx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ const struct vmk80xx_board *boardinfo;
+ struct vmk80xx_private *devpriv;
+ int ret;
+
+ boardinfo = &vmk80xx_boardinfo[context];
+ dev->board_ptr = boardinfo;
+ dev->board_name = boardinfo->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->model = boardinfo->model;
+
+ ret = vmk80xx_find_usb_endpoints(dev);
+ if (ret)
+ return ret;
+
+ ret = vmk80xx_alloc_usb_buffers(dev);
+ if (ret)
+ return ret;
+
+ sema_init(&devpriv->limit_sem, 8);
+
+ usb_set_intfdata(intf, devpriv);
+
+ if (devpriv->model == VMK8055_MODEL)
+ vmk80xx_reset_device(dev);
+
+ return vmk80xx_init_subdevices(dev);
+}
+
+static void vmk80xx_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct vmk80xx_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->limit_sem);
+
+ usb_set_intfdata(intf, NULL);
+
+ kfree(devpriv->usb_rx_buf);
+ kfree(devpriv->usb_tx_buf);
+
+ up(&devpriv->limit_sem);
+}
+
+static struct comedi_driver vmk80xx_driver = {
+ .module = THIS_MODULE,
+ .driver_name = "vmk80xx",
+ .auto_attach = vmk80xx_auto_attach,
+ .detach = vmk80xx_detach,
+};
+
+static int vmk80xx_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info);
+}
+
+static const struct usb_device_id vmk80xx_usb_id_table[] = {
+ { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 },
+ { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 },
+ { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 },
+ { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 },
+ { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 },
+ { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 },
+ { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 },
+ { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 },
+ { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 },
+ { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 },
+ { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 },
+ { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table);
+
+static struct usb_driver vmk80xx_usb_driver = {
+ .name = "vmk80xx",
+ .id_table = vmk80xx_usb_id_table,
+ .probe = vmk80xx_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+};
+module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver);
+
+MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>");
+MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver");
+MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/z8536.h b/drivers/staging/comedi/drivers/z8536.h
new file mode 100644
index 000000000..7be53109c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/z8536.h
@@ -0,0 +1,202 @@
+/*
+ * Z8536 CIO Internal registers
+ */
+
+#ifndef _Z8536_H
+#define _Z8536_H
+
+/* Master Interrupt Control register */
+#define Z8536_INT_CTRL_REG 0x00
+#define Z8536_INT_CTRL_MIE BIT(7) /* Master Interrupt Enable */
+#define Z8536_INT_CTRL_DLC BIT(6) /* Disable Lower Chain */
+#define Z8536_INT_CTRL_NV BIT(5) /* No Vector */
+#define Z8536_INT_CTRL_PA_VIS BIT(4) /* Port A Vect Inc Status */
+#define Z8536_INT_CTRL_PB_VIS BIT(3) /* Port B Vect Inc Status */
+#define Z8536_INT_CTRL_VT_VIS BIT(2) /* C/T Vect Inc Status */
+#define Z8536_INT_CTRL_RJA BIT(1) /* Right Justified Addresses */
+#define Z8536_INT_CTRL_RESET BIT(0) /* Reset */
+
+/* Master Configuration Control register */
+#define Z8536_CFG_CTRL_REG 0x01
+#define Z8536_CFG_CTRL_PBE BIT(7) /* Port B Enable */
+#define Z8536_CFG_CTRL_CT1E BIT(6) /* C/T 1 Enable */
+#define Z8536_CFG_CTRL_CT2E BIT(5) /* C/T 2 Enable */
+#define Z8536_CFG_CTRL_PCE_CT3E BIT(4) /* Port C & C/T 3 Enable */
+#define Z8536_CFG_CTRL_PLC BIT(3) /* Port A/B Link Control */
+#define Z8536_CFG_CTRL_PAE BIT(2) /* Port A Enable */
+#define Z8536_CFG_CTRL_LC_INDEP (0 << 0)/* C/Ts Independent */
+#define Z8536_CFG_CTRL_LC_GATE (1 << 0)/* C/T 1 Out Gates C/T 2 */
+#define Z8536_CFG_CTRL_LC_TRIG (2 << 0)/* C/T 1 Out Triggers C/T 2 */
+#define Z8536_CFG_CTRL_LC_CLK (3 << 0)/* C/T 1 Out Clocks C/T 2 */
+#define Z8536_CFG_CTRL_LC_MASK (3 << 0)/* C/T Link Control mask */
+
+/* Interrupt Vector registers */
+#define Z8536_PA_INT_VECT_REG 0x02
+#define Z8536_PB_INT_VECT_REG 0x03
+#define Z8536_CT_INT_VECT_REG 0x04
+#define Z8536_CURR_INT_VECT_REG 0x1f
+
+/* Port A/B & Counter/Timer 1/2/3 Command and Status registers */
+#define Z8536_PA_CMDSTAT_REG 0x08
+#define Z8536_PB_CMDSTAT_REG 0x09
+#define Z8536_CT1_CMDSTAT_REG 0x0a
+#define Z8536_CT2_CMDSTAT_REG 0x0b
+#define Z8536_CT3_CMDSTAT_REG 0x0c
+#define Z8536_CT_CMDSTAT_REG(x) (0x0a + (x))
+#define Z8536_CMD_NULL (0 << 5)/* Null Code */
+#define Z8536_CMD_CLR_IP_IUS (1 << 5)/* Clear IP & IUS */
+#define Z8536_CMD_SET_IUS (2 << 5)/* Set IUS */
+#define Z8536_CMD_CLR_IUS (3 << 5)/* Clear IUS */
+#define Z8536_CMD_SET_IP (4 << 5)/* Set IP */
+#define Z8536_CMD_CLR_IP (5 << 5)/* Clear IP */
+#define Z8536_CMD_SET_IE (6 << 5)/* Set IE */
+#define Z8536_CMD_CLR_IE (7 << 5)/* Clear IE */
+#define Z8536_CMD_MASK (7 << 5)
+
+#define Z8536_STAT_IUS BIT(7) /* Interrupt Under Service */
+#define Z8536_STAT_IE BIT(6) /* Interrupt Enable */
+#define Z8536_STAT_IP BIT(5) /* Interrupt Pending */
+#define Z8536_STAT_ERR BIT(4) /* Interrupt Error */
+#define Z8536_STAT_IE_IP (Z8536_STAT_IE | Z8536_STAT_IP)
+
+#define Z8536_PAB_STAT_ORE BIT(3) /* Output Register Empty */
+#define Z8536_PAB_STAT_IRF BIT(2) /* Input Register Full */
+#define Z8536_PAB_STAT_PMF BIT(1) /* Pattern Match Flag */
+#define Z8536_PAB_CMDSTAT_IOE BIT(0) /* Interrupt On Error */
+
+#define Z8536_CT_CMD_RCC BIT(3) /* Read Counter Control */
+#define Z8536_CT_CMDSTAT_GCB BIT(2) /* Gate Command Bit */
+#define Z8536_CT_CMD_TCB BIT(1) /* Trigger Command Bit */
+#define Z8536_CT_STAT_CIP BIT(0) /* Count In Progress */
+
+/* Port Data registers */
+#define Z8536_PA_DATA_REG 0x0d
+#define Z8536_PB_DATA_REG 0x0e
+#define Z8536_PC_DATA_REG 0x0f
+
+/* Counter/Timer 1/2/3 Current Count registers */
+#define Z8536_CT1_VAL_MSB_REG 0x10
+#define Z8536_CT1_VAL_LSB_REG 0x11
+#define Z8536_CT2_VAL_MSB_REG 0x12
+#define Z8536_CT2_VAL_LSB_REG 0x13
+#define Z8536_CT3_VAL_MSB_REG 0x14
+#define Z8536_CT3_VAL_LSB_REG 0x15
+#define Z8536_CT_VAL_MSB_REG(x) (0x10 + ((x) * 2))
+#define Z8536_CT_VAL_LSB_REG(x) (0x11 + ((x) * 2))
+
+/* Counter/Timer 1/2/3 Time Constant registers */
+#define Z8536_CT1_RELOAD_MSB_REG 0x16
+#define Z8536_CT1_RELOAD_LSB_REG 0x17
+#define Z8536_CT2_RELOAD_MSB_REG 0x18
+#define Z8536_CT2_RELOAD_LSB_REG 0x19
+#define Z8536_CT3_RELOAD_MSB_REG 0x1a
+#define Z8536_CT3_RELOAD_LSB_REG 0x1b
+#define Z8536_CT_RELOAD_MSB_REG(x) (0x16 + ((x) * 2))
+#define Z8536_CT_RELOAD_LSB_REG(x) (0x17 + ((x) * 2))
+
+/* Counter/Timer 1/2/3 Mode Specification registers */
+#define Z8536_CT1_MODE_REG 0x1c
+#define Z8536_CT2_MODE_REG 0x1d
+#define Z8536_CT3_MODE_REG 0x1e
+#define Z8536_CT_MODE_REG(x) (0x1c + (x))
+#define Z8536_CT_MODE_CSC BIT(7) /* Continuous/Single Cycle */
+#define Z8536_CT_MODE_EOE BIT(6) /* External Output Enable */
+#define Z8536_CT_MODE_ECE BIT(5) /* External Count Enable */
+#define Z8536_CT_MODE_ETE BIT(4) /* External Trigger Enable */
+#define Z8536_CT_MODE_EGE BIT(3) /* External Gate Enable */
+#define Z8536_CT_MODE_REB BIT(2) /* Retrigger Enable Bit */
+#define Z8536_CT_MODE_DCS_PULSE (0 << 0)/* Duty Cycle - Pulse */
+#define Z8536_CT_MODE_DCS_ONESHOT (1 << 0)/* Duty Cycle - One-Shot */
+#define Z8536_CT_MODE_DCS_SQRWAVE (2 << 0)/* Duty Cycle - Square Wave */
+#define Z8536_CT_MODE_DCS_DO_NOT_USE (3 << 0)/* Duty Cycle - Do Not Use */
+#define Z8536_CT_MODE_DCS_MASK (3 << 0)/* Duty Cycle mask */
+
+/* Port A/B Mode Specification registers */
+#define Z8536_PA_MODE_REG 0x20
+#define Z8536_PB_MODE_REG 0x28
+#define Z8536_PAB_MODE_PTS_BIT (0 << 6)/* Bit Port */
+#define Z8536_PAB_MODE_PTS_INPUT (1 << 6)/* Input Port */
+#define Z8536_PAB_MODE_PTS_OUTPUT (2 << 6)/* Output Port */
+#define Z8536_PAB_MODE_PTS_BIDIR (3 << 6)/* Bidirectional Port */
+#define Z8536_PAB_MODE_PTS_MASK (3 << 6)/* Port Type Select mask */
+#define Z8536_PAB_MODE_ITB BIT(5) /* Interrupt on Two Bytes */
+#define Z8536_PAB_MODE_SB BIT(4) /* Single Buffered mode */
+#define Z8536_PAB_MODE_IMO BIT(3) /* Interrupt on Match Only */
+#define Z8536_PAB_MODE_PMS_DISABLE (0 << 1)/* Disable Pattern Match */
+#define Z8536_PAB_MODE_PMS_AND (1 << 1)/* "AND" mode */
+#define Z8536_PAB_MODE_PMS_OR (2 << 1)/* "OR" mode */
+#define Z8536_PAB_MODE_PMS_OR_PEV (3 << 1)/* "OR-Priority" mode */
+#define Z8536_PAB_MODE_PMS_MASK (3 << 1)/* Pattern Mode mask */
+#define Z8536_PAB_MODE_LPM BIT(0) /* Latch on Pattern Match */
+#define Z8536_PAB_MODE_DTE BIT(0) /* Deskew Timer Enabled */
+
+/* Port A/B Handshake Specification registers */
+#define Z8536_PA_HANDSHAKE_REG 0x21
+#define Z8536_PB_HANDSHAKE_REG 0x29
+#define Z8536_PAB_HANDSHAKE_HST_INTER (0 << 6)/* Interlocked Handshake */
+#define Z8536_PAB_HANDSHAKE_HST_STROBED (1 << 6)/* Strobed Handshake */
+#define Z8536_PAB_HANDSHAKE_HST_PULSED (2 << 6)/* Pulsed Handshake */
+#define Z8536_PAB_HANDSHAKE_HST_3WIRE (3 << 6)/* Three-Wire Handshake */
+#define Z8536_PAB_HANDSHAKE_HST_MASK (3 << 6)/* Handshake Type mask */
+#define Z8536_PAB_HANDSHAKE_RWS_DISABLE (0 << 3)/* Req/Wait Disabled */
+#define Z8536_PAB_HANDSHAKE_RWS_OUTWAIT (1 << 3)/* Output Wait */
+#define Z8536_PAB_HANDSHAKE_RWS_INWAIT (3 << 3)/* Input Wait */
+#define Z8536_PAB_HANDSHAKE_RWS_SPREQ (4 << 3)/* Special Request */
+#define Z8536_PAB_HANDSHAKE_RWS_OUTREQ (5 << 4)/* Output Request */
+#define Z8536_PAB_HANDSHAKE_RWS_INREQ (7 << 3)/* Input Request */
+#define Z8536_PAB_HANDSHAKE_RWS_MASK (7 << 3)/* Req/Wait mask */
+#define Z8536_PAB_HANDSHAKE_DESKEW(x) ((x) << 0)/* Deskew Time */
+#define Z8536_PAB_HANDSHAKE_DESKEW_MASK (3 << 0)/* Deskew Time mask */
+
+/*
+ * Port A/B/C Data Path Polarity registers
+ *
+ * 0 = Non-Inverting
+ * 1 = Inverting
+ */
+#define Z8536_PA_DPP_REG 0x22
+#define Z8536_PB_DPP_REG 0x2a
+#define Z8536_PC_DPP_REG 0x05
+
+/*
+ * Port A/B/C Data Direction registers
+ *
+ * 0 = Output bit
+ * 1 = Input bit
+ */
+#define Z8536_PA_DD_REG 0x23
+#define Z8536_PB_DD_REG 0x2b
+#define Z8536_PC_DD_REG 0x06
+
+/*
+ * Port A/B/C Special I/O Control registers
+ *
+ * 0 = Normal Input or Output
+ * 1 = Output with open drain or Input with 1's catcher
+ */
+#define Z8536_PA_SIO_REG 0x24
+#define Z8536_PB_SIO_REG 0x2c
+#define Z8536_PC_SIO_REG 0x07
+
+/*
+ * Port A/B Pattern Polarity/Transition/Mask registers
+ *
+ * PM PT PP Pattern Specification
+ * -- -- -- -------------------------------------
+ * 0 0 x Bit masked off
+ * 0 1 x Any transition
+ * 1 0 0 Zero (low-level)
+ * 1 0 1 One (high-level)
+ * 1 1 0 One-to-zero transition (falling-edge)
+ * 1 1 1 Zero-to-one transition (rising-edge)
+ */
+#define Z8536_PA_PP_REG 0x25
+#define Z8536_PB_PP_REG 0x2d
+
+#define Z8536_PA_PT_REG 0x26
+#define Z8536_PB_PT_REG 0x2e
+
+#define Z8536_PA_PM_REG 0x27
+#define Z8536_PB_PM_REG 0x2f
+
+#endif /* _Z8536_H */
diff --git a/drivers/staging/comedi/kcomedilib/Makefile b/drivers/staging/comedi/kcomedilib/Makefile
new file mode 100644
index 000000000..3aff8ed08
--- /dev/null
+++ b/drivers/staging/comedi/kcomedilib/Makefile
@@ -0,0 +1,5 @@
+ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG
+
+obj-$(CONFIG_COMEDI_KCOMEDILIB) += kcomedilib.o
+
+kcomedilib-objs := kcomedilib_main.o
diff --git a/drivers/staging/comedi/kcomedilib/kcomedilib_main.c b/drivers/staging/comedi/kcomedilib/kcomedilib_main.c
new file mode 100644
index 000000000..76bf5619f
--- /dev/null
+++ b/drivers/staging/comedi/kcomedilib/kcomedilib_main.c
@@ -0,0 +1,252 @@
+/*
+ kcomedilib/kcomedilib.c
+ a comedlib interface for kernel modules
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+
+#include "../comedi.h"
+#include "../comedilib.h"
+#include "../comedidev.h"
+
+MODULE_AUTHOR("David Schleef <ds@schleef.org>");
+MODULE_DESCRIPTION("Comedi kernel library");
+MODULE_LICENSE("GPL");
+
+struct comedi_device *comedi_open(const char *filename)
+{
+ struct comedi_device *dev, *retval = NULL;
+ unsigned int minor;
+
+ if (strncmp(filename, "/dev/comedi", 11) != 0)
+ return NULL;
+
+ if (kstrtouint(filename + 11, 0, &minor))
+ return NULL;
+
+ if (minor >= COMEDI_NUM_BOARD_MINORS)
+ return NULL;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return NULL;
+
+ down_read(&dev->attach_lock);
+ if (dev->attached)
+ retval = dev;
+ else
+ retval = NULL;
+ up_read(&dev->attach_lock);
+
+ if (!retval)
+ comedi_dev_put(dev);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(comedi_open);
+
+int comedi_close(struct comedi_device *dev)
+{
+ comedi_dev_put(dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_close);
+
+static int comedi_do_insn(struct comedi_device *dev,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ mutex_lock(&dev->mutex);
+
+ if (!dev->attached) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* a subdevice instruction */
+ if (insn->subdev >= dev->n_subdevices) {
+ ret = -EINVAL;
+ goto error;
+ }
+ s = &dev->subdevices[insn->subdev];
+
+ if (s->type == COMEDI_SUBD_UNUSED) {
+ dev_err(dev->class_dev,
+ "%d not usable subdevice\n", insn->subdev);
+ ret = -EIO;
+ goto error;
+ }
+
+ /* XXX check lock */
+
+ ret = comedi_check_chanlist(s, 1, &insn->chanspec);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "bad chanspec\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (s->busy) {
+ ret = -EBUSY;
+ goto error;
+ }
+ s->busy = dev;
+
+ switch (insn->insn) {
+ case INSN_BITS:
+ ret = s->insn_bits(dev, s, insn, data);
+ break;
+ case INSN_CONFIG:
+ /* XXX should check instruction length */
+ ret = s->insn_config(dev, s, insn, data);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ s->busy = NULL;
+error:
+
+ mutex_unlock(&dev->mutex);
+ return ret;
+}
+
+int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,
+ unsigned int chan, unsigned int *io)
+{
+ struct comedi_insn insn;
+ unsigned int data[2];
+ int ret;
+
+ memset(&insn, 0, sizeof(insn));
+ insn.insn = INSN_CONFIG;
+ insn.n = 2;
+ insn.subdev = subdev;
+ insn.chanspec = CR_PACK(chan, 0, 0);
+ data[0] = INSN_CONFIG_DIO_QUERY;
+ data[1] = 0;
+ ret = comedi_do_insn(dev, &insn, data);
+ if (ret >= 0)
+ *io = data[1];
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_get_config);
+
+int comedi_dio_config(struct comedi_device *dev, unsigned int subdev,
+ unsigned int chan, unsigned int io)
+{
+ struct comedi_insn insn;
+
+ memset(&insn, 0, sizeof(insn));
+ insn.insn = INSN_CONFIG;
+ insn.n = 1;
+ insn.subdev = subdev;
+ insn.chanspec = CR_PACK(chan, 0, 0);
+
+ return comedi_do_insn(dev, &insn, &io);
+}
+EXPORT_SYMBOL_GPL(comedi_dio_config);
+
+int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
+ unsigned int mask, unsigned int *bits,
+ unsigned int base_channel)
+{
+ struct comedi_insn insn;
+ unsigned int data[2];
+ unsigned int n_chan;
+ unsigned int shift;
+ int ret;
+
+ base_channel = CR_CHAN(base_channel);
+ n_chan = comedi_get_n_channels(dev, subdev);
+ if (base_channel >= n_chan)
+ return -EINVAL;
+
+ memset(&insn, 0, sizeof(insn));
+ insn.insn = INSN_BITS;
+ insn.chanspec = base_channel;
+ insn.n = 2;
+ insn.subdev = subdev;
+
+ data[0] = mask;
+ data[1] = *bits;
+
+ /*
+ * Most drivers ignore the base channel in insn->chanspec.
+ * Fix this here if the subdevice has <= 32 channels.
+ */
+ if (n_chan <= 32) {
+ shift = base_channel;
+ if (shift) {
+ insn.chanspec = 0;
+ data[0] <<= shift;
+ data[1] <<= shift;
+ }
+ } else {
+ shift = 0;
+ }
+
+ ret = comedi_do_insn(dev, &insn, data);
+ *bits = data[1] >> shift;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_bitfield2);
+
+int comedi_find_subdevice_by_type(struct comedi_device *dev, int type,
+ unsigned int subd)
+{
+ struct comedi_subdevice *s;
+ int ret = -ENODEV;
+
+ down_read(&dev->attach_lock);
+ if (dev->attached)
+ for (; subd < dev->n_subdevices; subd++) {
+ s = &dev->subdevices[subd];
+ if (s->type == type) {
+ ret = subd;
+ break;
+ }
+ }
+ up_read(&dev->attach_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type);
+
+int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice)
+{
+ int n;
+
+ down_read(&dev->attach_lock);
+ if (!dev->attached || subdevice >= dev->n_subdevices)
+ n = 0;
+ else
+ n = dev->subdevices[subdevice].n_chan;
+ up_read(&dev->attach_lock);
+
+ return n;
+}
+EXPORT_SYMBOL_GPL(comedi_get_n_channels);
diff --git a/drivers/staging/comedi/proc.c b/drivers/staging/comedi/proc.c
new file mode 100644
index 000000000..91dea25b5
--- /dev/null
+++ b/drivers/staging/comedi/proc.c
@@ -0,0 +1,97 @@
+/*
+ * /proc interface for comedi
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * This is some serious bloatware.
+ *
+ * Taken from Dave A.'s PCL-711 driver, 'cuz I thought it
+ * was cool.
+ */
+
+#include "comedidev.h"
+#include "comedi_internal.h"
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+static int comedi_read(struct seq_file *m, void *v)
+{
+ int i;
+ int devices_q = 0;
+ struct comedi_driver *driv;
+
+ seq_printf(m, "comedi version " COMEDI_RELEASE "\nformat string: %s\n",
+ "\"%2d: %-20s %-20s %4d\", i, driver_name, board_name, n_subdevices");
+
+ for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) {
+ struct comedi_device *dev = comedi_dev_get_from_minor(i);
+
+ if (!dev)
+ continue;
+
+ down_read(&dev->attach_lock);
+ if (dev->attached) {
+ devices_q = 1;
+ seq_printf(m, "%2d: %-20s %-20s %4d\n",
+ i, dev->driver->driver_name,
+ dev->board_name, dev->n_subdevices);
+ }
+ up_read(&dev->attach_lock);
+ comedi_dev_put(dev);
+ }
+ if (!devices_q)
+ seq_puts(m, "no devices\n");
+
+ mutex_lock(&comedi_drivers_list_lock);
+ for (driv = comedi_drivers; driv; driv = driv->next) {
+ seq_printf(m, "%s:\n", driv->driver_name);
+ for (i = 0; i < driv->num_names; i++)
+ seq_printf(m, " %s\n",
+ *(char **)((char *)driv->board_name +
+ i * driv->offset));
+
+ if (!driv->num_names)
+ seq_printf(m, " %s\n", driv->driver_name);
+ }
+ mutex_unlock(&comedi_drivers_list_lock);
+
+ return 0;
+}
+
+/*
+ * seq_file wrappers for procfile show routines.
+ */
+static int comedi_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, comedi_read, NULL);
+}
+
+static const struct file_operations comedi_proc_fops = {
+ .open = comedi_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+void comedi_proc_init(void)
+{
+ proc_create("comedi", 0644, NULL, &comedi_proc_fops);
+}
+
+void comedi_proc_cleanup(void)
+{
+ remove_proc_entry("comedi", NULL);
+}
diff --git a/drivers/staging/comedi/range.c b/drivers/staging/comedi/range.c
new file mode 100644
index 000000000..6a393b24b
--- /dev/null
+++ b/drivers/staging/comedi/range.c
@@ -0,0 +1,132 @@
+/*
+ * comedi/range.c
+ * comedi routines for voltage ranges
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/uaccess.h>
+#include "comedidev.h"
+#include "comedi_internal.h"
+
+const struct comedi_lrange range_bipolar10 = { 1, {BIP_RANGE(10)} };
+EXPORT_SYMBOL_GPL(range_bipolar10);
+const struct comedi_lrange range_bipolar5 = { 1, {BIP_RANGE(5)} };
+EXPORT_SYMBOL_GPL(range_bipolar5);
+const struct comedi_lrange range_bipolar2_5 = { 1, {BIP_RANGE(2.5)} };
+EXPORT_SYMBOL_GPL(range_bipolar2_5);
+const struct comedi_lrange range_unipolar10 = { 1, {UNI_RANGE(10)} };
+EXPORT_SYMBOL_GPL(range_unipolar10);
+const struct comedi_lrange range_unipolar5 = { 1, {UNI_RANGE(5)} };
+EXPORT_SYMBOL_GPL(range_unipolar5);
+const struct comedi_lrange range_unipolar2_5 = { 1, {UNI_RANGE(2.5)} };
+EXPORT_SYMBOL_GPL(range_unipolar2_5);
+const struct comedi_lrange range_0_20mA = { 1, {RANGE_mA(0, 20)} };
+EXPORT_SYMBOL_GPL(range_0_20mA);
+const struct comedi_lrange range_4_20mA = { 1, {RANGE_mA(4, 20)} };
+EXPORT_SYMBOL_GPL(range_4_20mA);
+const struct comedi_lrange range_0_32mA = { 1, {RANGE_mA(0, 32)} };
+EXPORT_SYMBOL_GPL(range_0_32mA);
+const struct comedi_lrange range_unknown = { 1, {{0, 1000000, UNIT_none} } };
+EXPORT_SYMBOL_GPL(range_unknown);
+
+/*
+ * COMEDI_RANGEINFO ioctl
+ * range information
+ *
+ * arg:
+ * pointer to comedi_rangeinfo structure
+ *
+ * reads:
+ * comedi_rangeinfo structure
+ *
+ * writes:
+ * array of comedi_krange structures to rangeinfo->range_ptr pointer
+ */
+int do_rangeinfo_ioctl(struct comedi_device *dev,
+ struct comedi_rangeinfo __user *arg)
+{
+ struct comedi_rangeinfo it;
+ int subd, chan;
+ const struct comedi_lrange *lr;
+ struct comedi_subdevice *s;
+
+ if (copy_from_user(&it, arg, sizeof(struct comedi_rangeinfo)))
+ return -EFAULT;
+ subd = (it.range_type >> 24) & 0xf;
+ chan = (it.range_type >> 16) & 0xff;
+
+ if (!dev->attached)
+ return -EINVAL;
+ if (subd >= dev->n_subdevices)
+ return -EINVAL;
+ s = &dev->subdevices[subd];
+ if (s->range_table) {
+ lr = s->range_table;
+ } else if (s->range_table_list) {
+ if (chan >= s->n_chan)
+ return -EINVAL;
+ lr = s->range_table_list[chan];
+ } else {
+ return -EINVAL;
+ }
+
+ if (RANGE_LENGTH(it.range_type) != lr->length) {
+ dev_dbg(dev->class_dev,
+ "wrong length %d should be %d (0x%08x)\n",
+ RANGE_LENGTH(it.range_type),
+ lr->length, it.range_type);
+ return -EINVAL;
+ }
+
+ if (copy_to_user(it.range_ptr, lr->range,
+ sizeof(struct comedi_krange) * lr->length))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * comedi_check_chanlist() - Validate each element in a chanlist.
+ * @s: comedi_subdevice struct
+ * @n: number of elements in the chanlist
+ * @chanlist: the chanlist to validate
+*/
+int comedi_check_chanlist(struct comedi_subdevice *s, int n,
+ unsigned int *chanlist)
+{
+ struct comedi_device *dev = s->device;
+ unsigned int chanspec;
+ int chan, range_len, i;
+
+ for (i = 0; i < n; i++) {
+ chanspec = chanlist[i];
+ chan = CR_CHAN(chanspec);
+ if (s->range_table)
+ range_len = s->range_table->length;
+ else if (s->range_table_list && chan < s->n_chan)
+ range_len = s->range_table_list[chan]->length;
+ else
+ range_len = 0;
+ if (chan >= s->n_chan ||
+ CR_RANGE(chanspec) >= range_len) {
+ dev_warn(dev->class_dev,
+ "bad chanlist[%d]=0x%08x chan=%d range length=%d\n",
+ i, chanspec, chan, range_len);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_check_chanlist);