diff options
Diffstat (limited to 'drivers/staging/comedi')
173 files changed, 111515 insertions, 0 deletions
diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig new file mode 100644 index 00000000000..a2f6957e7ee --- /dev/null +++ b/drivers/staging/comedi/Kconfig @@ -0,0 +1,1293 @@ +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_KCOMEDILIB + tristate "Comedi kcomedilib" + ---help--- + Build the kcomedilib + +config COMEDI_BOND + tristate "Device bonding support" + depends on COMEDI_KCOMEDILIB + ---help--- + Enable support for a driver to 'bond' (merge) multiple subdevices + from multiple devices together as one. + + 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" + select COMEDI_FC + ---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_SKEL + tristate "Comedi skeleton driver" + ---help--- + Build the Skeleton driver, an example for driver writers + + To compile this driver as a module, choose M here: the module will be + called skel. + +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" + ---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) + + 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 + + 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" + depends on VIRT_TO_BUS && ISA_DMA_API + select COMEDI_FC + ---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" + depends on VIRT_TO_BUS && ISA_DMA_API + select COMEDI_FC + ---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" + depends on VIRT_TO_BUS && ISA_DMA_API + select COMEDI_FC + ---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_8255 + select COMEDI_FC + ---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" + depends on ISA_DMA_API + select COMEDI_8255 + select COMEDI_FC + ---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_FC + ---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" + depends on VIRT_TO_BUS && ISA_DMA_API + select COMEDI_FC + ---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" + ---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_FC + depends on VIRT_TO_BUS && 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" + ---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" + ---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_FC + depends on VIRT_TO_BUS && ISA_DMA_API + ---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" + ---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 + select COMEDI_FC + ---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 && VIRT_TO_BUS + ---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 + bool "Comedi PCI drivers" + depends on PCI + ---help--- + Enable support for comedi PCI drivers. + +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_035 + tristate "ADDI-DATA APCI_035 support" + ---help--- + Enable support for ADDI-DATA APCI_035 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_035. + +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 VIRT_TO_BUS + select COMEDI_FC + ---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_FC + ---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" + select COMEDI_FC + depends on VIRT_TO_BUS + ---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_FC + ---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_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_pc236. + +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_FC + ---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_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" + select COMEDI_FC + ---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" + select COMEDI_FC + ---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 + select COMEDI_FC + ---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_8255 + select COMEDI_FC + ---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 support" + select COMEDI_8255 + ---help--- + Enable support for ComputerBoards/MeasurementComputing PCI Migration + series PCIM-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" + ---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" + depends on HAS_DMA + select COMEDI_MITE + ---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" + depends on HAS_DMA + select COMEDI_MITE + ---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" + depends on HAS_DMA + select COMEDI_NI_LABPC + select COMEDI_MITE + ---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 + select COMEDI_FC + ---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" + select COMEDI_FC + ---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 + select COMEDI_FC + 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 + bool "Comedi PCMCIA drivers" + depends on PCMCIA + ---help--- + Enable support for comedi PCMCIA drivers. + +if COMEDI_PCMCIA_DRIVERS + +config COMEDI_CB_DAS16_CS + tristate "CB DAS16 series PCMCIA support" + ---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 + select COMEDI_FC + ---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" + select COMEDI_FC + ---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 + bool "Comedi USB drivers" + depends on USB + ---help--- + Enable support for comedi USB drivers. + +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_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" + select COMEDI_FC + ---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" + select COMEDI_FC + ---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_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_FC + tristate + +config COMEDI_AMPLC_DIO200 + tristate + +config COMEDI_AMPLC_PC236 + tristate + select COMEDI_8255 + +config COMEDI_DAS08 + tristate + select COMEDI_8255 + +config COMEDI_NI_LABPC + tristate + select COMEDI_8255 + select COMEDI_FC + +config COMEDI_NI_LABPC_ISADMA + tristate + +config COMEDI_NI_TIO + tristate + +endif # COMEDI diff --git a/drivers/staging/comedi/Makefile b/drivers/staging/comedi/Makefile new file mode 100644 index 00000000000..fae2d909000 --- /dev/null +++ b/drivers/staging/comedi/Makefile @@ -0,0 +1,14 @@ +ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG + +comedi-y := comedi_fops.o range.o drivers.o \ + comedi_buf.o +comedi-$(CONFIG_COMEDI_PCI_DRIVERS) += comedi_pci.o +comedi-$(CONFIG_COMEDI_PCMCIA_DRIVERS) += comedi_pcmcia.o +comedi-$(CONFIG_COMEDI_USB_DRIVERS) += comedi_usb.o +comedi-$(CONFIG_PROC_FS) += proc.o +comedi-$(CONFIG_COMPAT) += comedi_compat32.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 00000000000..b68fbdb5eeb --- /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 00000000000..6bbbe5b0895 --- /dev/null +++ b/drivers/staging/comedi/comedi.h @@ -0,0 +1,979 @@ +/* + 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_BOGUS 0x0001 /* do the motions */ +#define TRIG_DITHER 0x0002 /* enable dithering */ +#define TRIG_DEGLITCH 0x0004 /* enable deglitching */ + /*#define TRIG_RT 0x0008 *//* perform op in real time */ +#define TRIG_CONFIG 0x0010 /* perform configuration, not triggering */ +#define TRIG_WAKE_EOS 0x0020 /* wake up on end-of-scan events */ + /*#define TRIG_WRITE 0x0040*//* write to bidirectional devices */ + +/* command flags */ +/* These flags are used in comedi_cmd structures */ + +/* try to use a real-time interrupt while performing command */ +#define CMDF_PRIORITY 0x00000008 + +#define TRIG_RT CMDF_PRIORITY /* compatibility definition */ + +#define CMDF_WRITE 0x00000040 +#define TRIG_WRITE CMDF_WRITE /* compatibility definition */ + +#define CMDF_RAWDATA 0x00000080 + +#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 + +#define TRIG_ROUND_MASK 0x00030000 +#define TRIG_ROUND_NEAREST 0x00000000 +#define TRIG_ROUND_DOWN 0x00010000 +#define TRIG_ROUND_UP 0x00020000 +#define TRIG_ROUND_UP_NEXT 0x00030000 + +/* 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) + +/* 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) + +/* callback stuff */ +/* only relevant to kernel modules. */ + +#define COMEDI_CB_EOS 1 /* end of scan */ +#define COMEDI_CB_EOA 2 /* end of acquisition/output */ +#define COMEDI_CB_BLOCK 4 /* data has arrived: + * wakes up read() / write() */ +#define COMEDI_CB_EOBUF 8 /* DEPRECATED: end of buffer */ +#define COMEDI_CB_ERROR 16 /* card error during acquisition */ +#define COMEDI_CB_OVERFLOW 32 /* buffer overflow/underflow */ + +/**********************************************************/ +/* 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 +}; + +static inline unsigned NI_USUAL_PFI_SELECT(unsigned pfi_channel) +{ + if (pfi_channel < 10) + return 0x1 + pfi_channel; + else + return 0xb + pfi_channel; +} + +static inline unsigned NI_USUAL_RTSI_SELECT(unsigned rtsi_channel) +{ + if (rtsi_channel < 7) + return 0xb + rtsi_channel; + else + return 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 +}; +static inline unsigned NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(unsigned n) +{ + /* NI 660x-specific */ + return 0x10 + n; +} +static inline unsigned NI_GPCT_RTSI_CLOCK_SRC_BITS(unsigned n) +{ + return 0x18 + n; +} +static inline unsigned NI_GPCT_PFI_CLOCK_SRC_BITS(unsigned n) +{ + /* no pfi on NI 660x */ + return 0x20 + n; +} + +/* 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, +}; +static inline unsigned NI_GPCT_GATE_PIN_GATE_SELECT(unsigned n) +{ + return 0x102 + n; +} +static inline unsigned NI_GPCT_RTSI_GATE_SELECT(unsigned n) +{ + return NI_USUAL_RTSI_SELECT(n); +} +static inline unsigned NI_GPCT_PFI_GATE_SELECT(unsigned n) +{ + return NI_USUAL_PFI_SELECT(n); +} +static inline unsigned NI_GPCT_UP_DOWN_PIN_GATE_SELECT(unsigned n) +{ + return 0x202 + n; +} + +/* 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, +}; +static inline unsigned NI_GPCT_PFI_OTHER_SELECT(unsigned n) +{ + return NI_USUAL_PFI_SELECT(n); +} + +/* 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 +}; +static inline unsigned NI_MIO_PLL_RTSI_CLOCK(unsigned rtsi_channel) +{ + return NI_MIO_PLL_RTSI0_CLOCK + rtsi_channel; +} + +/* 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 */ +}; +static inline unsigned NI_RTSI_OUTPUT_RTSI_BRD(unsigned n) +{ + return NI_RTSI_OUTPUT_RTSI_BRD_0 + n; +} + +/* 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 +}; +static inline unsigned NI_PFI_OUTPUT_RTSI(unsigned rtsi_channel) +{ + return NI_PFI_OUTPUT_RTSI0 + rtsi_channel; +} + +/* 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). */ +static inline unsigned NI_EXT_PFI(unsigned pfi_channel) +{ + return NI_USUAL_PFI_SELECT(pfi_channel) - 1; +} +static inline unsigned NI_EXT_RTSI(unsigned rtsi_channel) +{ + return NI_USUAL_RTSI_SELECT(rtsi_channel) - 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 +}; +static inline unsigned NI_CDIO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) +{ + return NI_USUAL_PFI_SELECT(pfi_channel); +} +static inline unsigned NI_CDIO_SCAN_BEGIN_SRC_RTSI(unsigned rtsi_channel) +{ + return NI_USUAL_RTSI_SELECT(rtsi_channel); +} + +/* 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. */ +static inline unsigned NI_AO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) +{ + return NI_USUAL_PFI_SELECT(pfi_channel); +} +static inline unsigned NI_AO_SCAN_BEGIN_SRC_RTSI(unsigned rtsi_channel) +{ + return NI_USUAL_RTSI_SELECT(rtsi_channel); +} + +/* 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" */ +}; + +#endif /* _COMEDI_H */ diff --git a/drivers/staging/comedi/comedi_buf.c b/drivers/staging/comedi/comedi_buf.c new file mode 100644 index 00000000000..df4a9c4bca3 --- /dev/null +++ b/drivers/staging/comedi/comedi_buf.c @@ -0,0 +1,511 @@ +/* + * comedi_buf.c + * + * 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/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->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; +} + +static unsigned int __comedi_buf_write_alloc(struct comedi_subdevice *s, + unsigned int nbytes, + int strict) +{ + struct comedi_async *async = s->async; + unsigned int available = comedi_buf_write_n_available(s); + + if (nbytes > available) + nbytes = strict ? 0 : 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; +} + +/* allocates chunk for the writer from free buffer space */ +unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, + unsigned int nbytes) +{ + return __comedi_buf_write_alloc(s, nbytes, 0); +} +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 = 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); + +int comedi_buf_put(struct comedi_subdevice *s, unsigned short x) +{ + struct comedi_async *async = s->async; + unsigned int n = __comedi_buf_write_alloc(s, sizeof(short), 1); + + if (n < sizeof(short)) { + async->events |= COMEDI_CB_ERROR; + return 0; + } + *(unsigned short *)(async->prealloc_buf + async->buf_write_ptr) = x; + comedi_buf_write_free(s, sizeof(short)); + return 1; +} +EXPORT_SYMBOL_GPL(comedi_buf_put); + +int comedi_buf_get(struct comedi_subdevice *s, unsigned short *x) +{ + struct comedi_async *async = s->async; + unsigned int n = comedi_buf_read_n_available(s); + + if (n < sizeof(short)) + return 0; + comedi_buf_read_alloc(s, sizeof(short)); + *x = *(unsigned short *)(async->prealloc_buf + async->buf_read_ptr); + comedi_buf_read_free(s, sizeof(short)); + return 1; +} +EXPORT_SYMBOL_GPL(comedi_buf_get); + +void comedi_buf_memcpy_to(struct comedi_subdevice *s, unsigned int offset, + const void *data, unsigned int num_bytes) +{ + struct comedi_async *async = s->async; + unsigned int write_ptr = async->buf_write_ptr + offset; + + if (write_ptr >= async->prealloc_bufsz) + write_ptr %= async->prealloc_bufsz; + + 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; + } +} +EXPORT_SYMBOL_GPL(comedi_buf_memcpy_to); + +void comedi_buf_memcpy_from(struct comedi_subdevice *s, unsigned int offset, + void *dest, unsigned int nbytes) +{ + void *src; + struct comedi_async *async = s->async; + unsigned int read_ptr = async->buf_read_ptr + offset; + + if (read_ptr >= async->prealloc_bufsz) + read_ptr %= async->prealloc_bufsz; + + 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; + } +} +EXPORT_SYMBOL_GPL(comedi_buf_memcpy_from); diff --git a/drivers/staging/comedi/comedi_compat32.c b/drivers/staging/comedi/comedi_compat32.c new file mode 100644 index 00000000000..1e9da405d83 --- /dev/null +++ b/drivers/staging/comedi/comedi_compat32.c @@ -0,0 +1,452 @@ +/* + 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, but that wasn't implemented + * until kernel version 2.6.11.) */ + 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; + + cmd32 = compat_ptr(arg); + cmd = compat_alloc_user_space(sizeof(*cmd)); + + rc = get_compat_cmd(cmd, cmd32); + if (rc) + return rc; + + return translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd); +} + +/* 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); +} + +/* Process untranslated ioctl. */ +/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */ +static inline int raw_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: + /* 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; +} + +/* compat_ioctl file operation. */ +/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */ +long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return raw_ioctl(file, cmd, arg); +} diff --git a/drivers/staging/comedi/comedi_compat32.h b/drivers/staging/comedi/comedi_compat32.h new file mode 100644 index 00000000000..28e3c305903 --- /dev/null +++ b/drivers/staging/comedi/comedi_compat32.h @@ -0,0 +1,37 @@ +/* + 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; +extern long comedi_compat_ioctl(struct file *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 00000000000..9d99fb3c18a --- /dev/null +++ b/drivers/staging/comedi/comedi_fops.c @@ -0,0 +1,2661 @@ +/* + 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. +*/ + +#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" + +#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); +} + +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 == NULL) + 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; +} + +struct comedi_device *comedi_dev_get_from_minor(unsigned minor) +{ + if (minor < COMEDI_NUM_BOARD_MINORS) + return comedi_dev_get_from_board_minor(minor); + else + 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 == NULL || (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 == NULL || (s->subdev_flags & SDF_CMD_WRITE)) + return s; + } + return dev->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, new_size); + 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_set_subdevice_runflags(struct comedi_subdevice *s, + unsigned mask, unsigned bits) +{ + unsigned long flags; + + spin_lock_irqsave(&s->spin_lock, flags); + s->runflags &= ~mask; + s->runflags |= (bits & mask); + spin_unlock_irqrestore(&s->spin_lock, flags); +} + +static unsigned comedi_get_subdevice_runflags(struct comedi_subdevice *s) +{ + unsigned long flags; + unsigned runflags; + + spin_lock_irqsave(&s->spin_lock, flags); + runflags = s->runflags; + spin_unlock_irqrestore(&s->spin_lock, flags); + return runflags; +} + +bool comedi_is_subdevice_running(struct comedi_subdevice *s) +{ + unsigned runflags = comedi_get_subdevice_runflags(s); + + return (runflags & SRF_RUNNING) ? true : false; +} +EXPORT_SYMBOL_GPL(comedi_is_subdevice_running); + +static bool comedi_is_subdevice_in_error(struct comedi_subdevice *s) +{ + unsigned runflags = comedi_get_subdevice_runflags(s); + + return (runflags & SRF_ERROR) ? true : false; +} + +static bool comedi_is_subdevice_idle(struct comedi_subdevice *s) +{ + unsigned runflags = comedi_get_subdevice_runflags(s); + + return (runflags & (SRF_ERROR | SRF_RUNNING)) ? false : true; +} + +/** + * 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 |= 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_set_subdevice_runflags(s, 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(&s->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 + device config ioctl + + arg: + pointer to devconfig structure + + reads: + devconfig structure at arg + + writes: + none +*/ +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 == NULL) { + 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 + buffer configuration ioctl + + arg: + pointer to bufconfig structure + + reads: + bufconfig at arg + + writes: + modified bufconfig at arg + +*/ +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 + device info ioctl + + arg: + pointer to devinfo structure + + reads: + none + + writes: + devinfo structure + +*/ +static int do_devinfo_ioctl(struct comedi_device *dev, + struct comedi_devinfo __user *arg, + struct file *file) +{ + const unsigned minor = iminor(file_inode(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_read_subdevice(dev, minor); + if (s) + devinfo.read_subdevice = s->index; + else + devinfo.read_subdevice = -1; + + s = comedi_write_subdevice(dev, minor); + 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 + subdevice info ioctl + + arg: + pointer to array of subdevice info structures + + reads: + none + + writes: + array of subdevice info structures at arg + +*/ +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 + subdevice info ioctl + + arg: + pointer to chaninfo structure + + reads: + chaninfo structure at arg + + writes: + arrays at elements of chaninfo structure + +*/ +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 + buffer information ioctl + + arg: + pointer to bufinfo structure + + reads: + bufinfo at arg + + writes: + modified bufinfo at arg + + */ +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]; + + if (s->lock && s->lock != file) + return -EACCES; + + 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 && (s->subdev_flags & SDF_CMD_READ)) { + 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) && + async->buf_write_count == async->buf_read_count) { + do_become_nonbusy(dev, s); + } + } + + if (bi.bytes_written && (s->subdev_flags & SDF_CMD_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("comedi: No check for data length of config insn id %i is implemented.\n", + data[0]); + pr_warn("comedi: Add a check to %s in %s.\n", + __func__, __FILE__); + pr_warn("comedi: 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 shift; + unsigned int orig_mask; + + orig_mask = data[0]; + if (s->n_chan <= 32) { + shift = CR_CHAN(insn->chanspec); + if (shift > 0) { + insn->chanspec = 0; + data[0] <<= shift; + data[1] <<= shift; + } + } else + shift = 0; + 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 + * synchronous instructions + * + * arg: + * pointer to sync cmd structure + * + * reads: + * sync cmd struct at arg + * instruction list + * data (for writes) + * + * writes: + * data (for reads) + */ +/* 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(sizeof(unsigned int) * MAX_SAMPLES, 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 + * synchronous instructions + * + * arg: + * pointer to insn + * + * reads: + * struct comedi_insn struct at arg + * data (for writes) + * + * writes: + * data (for reads) + */ +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(sizeof(unsigned int) * MAX_SAMPLES, 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; + } + + 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; + + /* user_chanlist could be NULL for do_cmdtest ioctls */ + if (!user_chanlist) + return 0; + + 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; +} + +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 & TRIG_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_EOA | COMEDI_CB_BLOCK | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW; + if (async->cmd.flags & TRIG_WAKE_EOS) + async->cb_mask |= COMEDI_CB_EOS; + + comedi_set_subdevice_runflags(s, SRF_ERROR | SRF_RUNNING, SRF_RUNNING); + + /* set s->busy _after_ setting 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 + command testing ioctl + + arg: + pointer to cmd structure + + reads: + cmd structure at arg + channel/range list + + writes: + modified cmd structure at arg + +*/ +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]; + + /* 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); + + /* 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 + lock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + +*/ + +static int do_lock_ioctl(struct comedi_device *dev, unsigned int 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); + +#if 0 + if (ret < 0) + return ret; + + if (s->lock_f) + ret = s->lock_f(dev, s); +#endif + + return ret; +} + +/* + COMEDI_UNLOCK + unlock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + + This function isn't protected by the semaphore, since + we already own the lock. +*/ +static int do_unlock_ioctl(struct comedi_device *dev, unsigned int 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) { +#if 0 + if (s->unlock) + s->unlock(dev, s); +#endif + + s->lock = NULL; + } + + return 0; +} + +/* + COMEDI_CANCEL + cancel acquisition ioctl + + arg: + subdevice number + + reads: + nothing + + writes: + nothing + +*/ +static int do_cancel_ioctl(struct comedi_device *dev, unsigned int arg, + void *file) +{ + struct comedi_subdevice *s; + int ret; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = &dev->subdevices[arg]; + if (s->async == NULL) + return -EINVAL; + + if (s->lock && s->lock != file) + return -EACCES; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + ret = do_cancel(dev, s); + + return ret; +} + +/* + 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 int arg, + void *file) +{ + struct comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = &dev->subdevices[arg]; + + if (s->lock && s->lock != file) + return -EACCES; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + if (s->poll) + return s->poll(dev, s); + + return -EINVAL; +} + +static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + const unsigned minor = iminor(file_inode(file)); + struct comedi_device *dev = file->private_data; + 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; + 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) +{ + const unsigned minor = iminor(file_inode(file)); + struct comedi_device *dev = file->private_data; + 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_write_subdevice(dev, minor); + else + s = comedi_read_subdevice(dev, minor); + 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; + const unsigned minor = iminor(file_inode(file)); + struct comedi_device *dev = file->private_data; + struct comedi_subdevice *s; + + mutex_lock(&dev->mutex); + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + goto done; + } + + s = comedi_read_subdevice(dev, minor); + if (s && s->async) { + poll_wait(file, &s->async->wait_head, wait); + if (!s->busy || !comedi_is_subdevice_running(s) || + comedi_buf_read_n_available(s) > 0) + mask |= POLLIN | POLLRDNORM; + } + + s = comedi_write_subdevice(dev, minor); + if (s && s->async) { + unsigned int bps = 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) || + 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); + const unsigned minor = iminor(file_inode(file)); + struct comedi_device *dev = file->private_data; + 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_write_subdevice(dev, minor); + 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; + } + + add_wait_queue(&async->wait_head, &wait); + on_wait_queue = true; + while (nbytes > 0 && !retval) { + set_current_state(TASK_INTERRUPTIBLE); + + if (!comedi_is_subdevice_running(s)) { + if (count == 0) { + struct comedi_subdevice *new_s; + + if (comedi_is_subdevice_in_error(s)) + 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_write_subdevice(dev, minor); + 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; + } + 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); + const unsigned minor = iminor(file_inode(file)); + struct comedi_device *dev = file->private_data; + 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_read_subdevice(dev, minor); + 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; + } + + 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); + /* printk("%d available\n",m); */ + if (async->buf_read_ptr + m > async->prealloc_bufsz) + m = async->prealloc_bufsz - async->buf_read_ptr; + /* printk("%d contiguous\n",m); */ + if (m < n) + n = m; + + if (n == 0) { + if (!comedi_is_subdevice_running(s)) { + if (comedi_is_subdevice_in_error(s)) + 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; + } + 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_read_subdevice(dev, minor); + if (dev->attached && old_detach_count == dev->detach_count && + s == new_s && new_s->async == async) { + if (become_nonbusy || + async->buf_read_count - async->buf_write_count == 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_device *dev = comedi_dev_get_from_minor(minor); + int rc; + + if (!dev) { + pr_debug("invalid minor number\n"); + return -ENODEV; + } + + 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 = dev; + rc = 0; + +out: + mutex_unlock(&dev->mutex); + if (rc) + comedi_dev_put(dev); + return rc; +} + +static int comedi_fasync(int fd, struct file *file, int on) +{ + struct comedi_device *dev = file->private_data; + + return fasync_helper(fd, file, on, &dev->async_queue); +} + +static int comedi_close(struct inode *inode, struct file *file) +{ + struct comedi_device *dev = file->private_data; + 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); + + 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, +}; + +void comedi_error(const struct comedi_device *dev, const char *s) +{ + dev_err(dev->class_dev, "%s: %s\n", dev->driver->driver_name, s); +} +EXPORT_SYMBOL_GPL(comedi_error); + +void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + unsigned runflags = 0; + unsigned runflags_mask = 0; + + if (!comedi_is_subdevice_running(s)) + return; + + if (s-> + async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW)) { + runflags_mask |= SRF_RUNNING; + } + /* remember if an error event has occurred, so an error + * can be returned the next time the user does a read() */ + if (s->async->events & (COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) { + runflags_mask |= SRF_ERROR; + runflags |= SRF_ERROR; + } + if (runflags_mask) { + /*sets SRF_ERROR and SRF_RUNNING together atomically */ + comedi_set_subdevice_runflags(s, runflags_mask, runflags); + } + + if (async->cb_mask & s->async->events) { + wake_up_interruptible(&async->wait_head); + if (s->subdev_flags & SDF_CMD_READ) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + if (s->subdev_flags & SDF_CMD_WRITE) + kill_fasync(&dev->async_queue, SIGIO, POLL_OUT); + } + s->async->events = 0; +} +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(struct comedi_device), GFP_KERNEL); + if (dev == NULL) + 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] == NULL) { + 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); + pr_err("comedi: error: 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] == NULL) { + comedi_subdevice_minor_table[i] = s; + break; + } + } + mutex_unlock(&comedi_subdevice_minor_table_lock); + if (i == COMEDI_NUM_SUBDEVICE_MINORS) { + pr_err("comedi: error: 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 == NULL) + 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("comedi: version " COMEDI_RELEASE " - http://www.comedi.org\n"); + + if (comedi_num_legacy_minors < 0 || + comedi_num_legacy_minors > COMEDI_NUM_BOARD_MINORS) { + pr_err("comedi: error: 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; + kobject_set_name(&comedi_cdev.kobj, "comedi"); + 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("comedi: 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); + } else { + /* 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 00000000000..e978c223f5b --- /dev/null +++ b/drivers/staging/comedi/comedi_internal.h @@ -0,0 +1,56 @@ +#ifndef _COMEDI_INTERNAL_H +#define _COMEDI_INTERNAL_H + +#include <linux/types.h> + +/* + * various internal comedi stuff + */ +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 00000000000..abbc0e4f5c5 --- /dev/null +++ b/drivers/staging/comedi/comedi_pci.c @@ -0,0 +1,146 @@ +/* + * 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/pci.h> + +#include "comedidev.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_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); diff --git a/drivers/staging/comedi/comedi_pcmcia.c b/drivers/staging/comedi/comedi_pcmcia.c new file mode 100644 index 00000000000..9d49d5d01ad --- /dev/null +++ b/drivers/staging/comedi/comedi_pcmcia.c @@ -0,0 +1,156 @@ +/* + * 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/kernel.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#include "comedidev.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); diff --git a/drivers/staging/comedi/comedi_usb.c b/drivers/staging/comedi/comedi_usb.c new file mode 100644 index 00000000000..13f18bef609 --- /dev/null +++ b/drivers/staging/comedi/comedi_usb.c @@ -0,0 +1,116 @@ +/* + * 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/usb.h> + +#include "comedidev.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); diff --git a/drivers/staging/comedi/comedidev.h b/drivers/staging/comedi/comedidev.h new file mode 100644 index 00000000000..8f4e44bfbe0 --- /dev/null +++ b/drivers/staging/comedi/comedidev.h @@ -0,0 +1,540 @@ +/* + 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 *); + /* int (*do_lock)(struct comedi_device *, struct comedi_subdevice *); */ + /* int (*do_unlock)(struct comedi_device *, \ + struct comedi_subdevice *); */ + + /* called when the buffer changes */ + int (*buf_change)(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size); + + 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; +}; + +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 { + void *prealloc_buf; /* pre-allocated buffer */ + unsigned int prealloc_bufsz; /* buffer size, in bytes */ + struct comedi_buf_map *buf_map; /* map of buffer pages */ + + unsigned int max_bufsize; /* maximum buffer size, bytes */ + + /* byte count for writer (write completed) */ + unsigned int buf_write_count; + /* byte count for writer (allocated for writing) */ + unsigned int buf_write_alloc_count; + /* byte count for reader (read completed) */ + unsigned int buf_read_count; + /* byte count for reader (allocated for reading) */ + unsigned int buf_read_alloc_count; + + unsigned int buf_write_ptr; /* buffer marker for writer */ + unsigned int buf_read_ptr; /* buffer marker for reader */ + + unsigned int cur_chan; /* useless channel marker for interrupt */ + /* number of bytes that have been received for current scan */ + unsigned int scan_progress; + /* keeps track of where we are in chanlist as for munging */ + unsigned int munge_chan; + /* number of bytes that have been munged */ + unsigned int munge_count; + /* buffer marker for munging */ + unsigned int munge_ptr; + + unsigned int events; /* events that have occurred */ + + 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); +}; + +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; + 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 */ + 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); +}; + +static inline const void *comedi_board(const struct comedi_device *dev) +{ + return dev->board_ptr; +} + +/* + * function prototypes + */ + +void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s); +void comedi_error(const struct comedi_device *dev, const char *s); + +/* we can expand the number of bits used to encode devices/subdevices into + the minor number soon, after more distros support > 8 bit minor numbers + (like after Debian Etch gets released) */ +enum comedi_minor_bits { + COMEDI_DEVICE_MINOR_MASK = 0xf, + COMEDI_SUBDEVICE_MINOR_MASK = 0xf0 +}; +static const unsigned COMEDI_SUBDEVICE_MINOR_SHIFT = 4; +static const unsigned COMEDI_SUBDEVICE_MINOR_OFFSET = 1; + +struct comedi_device *comedi_dev_get_from_minor(unsigned minor); +int comedi_dev_put(struct comedi_device *dev); + +void init_polling(void); +void cleanup_polling(void); +void start_polling(struct comedi_device *); +void stop_polling(struct comedi_device *); + +/* subdevice runflags */ +enum subdevice_runflags { + SRF_RT = 0x00000002, + /* indicates an COMEDI_CB_ERROR event has occurred since the last + * command was started */ + SRF_ERROR = 0x00000004, + SRF_RUNNING = 0x08000000, + SRF_FREE_SPRIV = 0x80000000, /* free s->private on detach */ +}; + +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_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; +} + +/* 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); +} + +static inline unsigned int bytes_per_sample(const struct comedi_subdevice *subd) +{ + if (subd->subdev_flags & SDF_LSAMPL) + return sizeof(unsigned int); + else + return sizeof(short); +} + +/* + * 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); + +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); + +int comedi_buf_put(struct comedi_subdevice *s, unsigned short x); +int comedi_buf_get(struct comedi_subdevice *s, unsigned short *x); + +void comedi_buf_memcpy_to(struct comedi_subdevice *s, unsigned int offset, + const void *source, unsigned int num_bytes); +void comedi_buf_memcpy_from(struct comedi_subdevice *s, unsigned int offset, + void *destination, unsigned int num_bytes); + +/* 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); + +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); + +void *comedi_alloc_devpriv(struct comedi_device *, size_t); +int comedi_alloc_subdevices(struct comedi_device *, int); + +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) + +#ifdef CONFIG_COMEDI_PCI_DRIVERS + +/* comedi_pci.c - comedi PCI driver specific functions */ + +/* + * 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; +struct pci_driver; + +struct pci_dev *comedi_to_pci_dev(struct comedi_device *); + +int comedi_pci_enable(struct comedi_device *); +void comedi_pci_disable(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)) + +#else + +/* + * Some of the comedi mixed ISA/PCI drivers call the PCI specific + * functions. Provide some dummy functions if CONFIG_COMEDI_PCI_DRIVERS + * is not enabled. + */ + +static inline struct pci_dev *comedi_to_pci_dev(struct comedi_device *dev) +{ + return NULL; +} + +static inline int comedi_pci_enable(struct comedi_device *dev) +{ + return -ENOSYS; +} + +static inline void comedi_pci_disable(struct comedi_device *dev) +{ +} + +#endif /* CONFIG_COMEDI_PCI_DRIVERS */ + +#ifdef CONFIG_COMEDI_PCMCIA_DRIVERS + +/* comedi_pcmcia.c - comedi PCMCIA driver specific functions */ + +struct pcmcia_driver; +struct pcmcia_device; + +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 /* CONFIG_COMEDI_PCMCIA_DRIVERS */ + +#ifdef CONFIG_COMEDI_USB_DRIVERS + +/* comedi_usb.c - comedi USB driver specific functions */ + +struct usb_driver; +struct usb_interface; + +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 /* CONFIG_COMEDI_USB_DRIVERS */ + +#endif /* _COMEDIDEV_H */ diff --git a/drivers/staging/comedi/comedilib.h b/drivers/staging/comedi/comedilib.h new file mode 100644 index 00000000000..56baf852ecf --- /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 00000000000..299726f39e2 --- /dev/null +++ b/drivers/staging/comedi/drivers.c @@ -0,0 +1,726 @@ +/* + module/drivers.c + functions for manipulating 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. +*/ + +#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; +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 != NULL) + 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); + +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 & SRF_FREE_SPRIV) + kfree(s->private); + comedi_free_subdevice_minor(s); + if (s->async) { + comedi_buf_alloc(dev, s, 0); + kfree(s->async); + } + } + kfree(dev->subdevices); + dev->subdevices = NULL; + dev->n_subdevices = 0; + } + kfree(dev->private); + dev->private = NULL; + dev->driver = NULL; + dev->board_name = NULL; + dev->board_ptr = 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_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); + +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, buf_size); + 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 = request_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 == NULL) { + /* 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 == NULL) { + /* 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; + } + /* initialize dev->driver here so + * comedi_error() can be called from attach */ + 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 == NULL) + 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/8253.h b/drivers/staging/comedi/drivers/8253.h new file mode 100644 index 00000000000..5829b46b757 --- /dev/null +++ b/drivers/staging/comedi/drivers/8253.h @@ -0,0 +1,345 @@ +/* + comedi/drivers/8253.h + Header file for 8253 + + 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 _8253_H +#define _8253_H + +#include "../comedi.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 + +static inline void i8253_cascade_ns_to_timer(int i8253_osc_base, + unsigned int *d1, + unsigned int *d2, + unsigned int *nanosec, + int round_mode) +{ + unsigned int divider; + unsigned int div1, div2; + unsigned int div1_glb, div2_glb, ns_glb; + unsigned int div1_lub, div2_lub, ns_lub; + unsigned int ns; + unsigned int start; + unsigned int ns_low, ns_high; + static const unsigned int max_count = 0x10000; + /* exit early if everything is already correct (this can save time + * since this function may be called repeatedly during command tests + * and execution) */ + div1 = *d1 ? *d1 : max_count; + div2 = *d2 ? *d2 : max_count; + divider = div1 * div2; + if (div1 * div2 * i8253_osc_base == *nanosec && + div1 > 1 && div1 <= max_count && div2 > 1 && div2 <= max_count && + /* check for overflow */ + divider > div1 && divider > div2 && + divider * i8253_osc_base > divider && + divider * i8253_osc_base > i8253_osc_base) { + return; + } + + divider = *nanosec / i8253_osc_base; + + div1_lub = div2_lub = 0; + div1_glb = div2_glb = 0; + + ns_glb = 0; + ns_lub = 0xffffffff; + + div2 = max_count; + start = divider / div2; + if (start < 2) + start = 2; + for (div1 = start; div1 <= divider / div1 + 1 && div1 <= max_count; + div1++) { + for (div2 = divider / div1; + div1 * div2 <= divider + div1 + 1 && div2 <= max_count; + div2++) { + ns = i8253_osc_base * div1 * div2; + if (ns <= *nanosec && ns > ns_glb) { + ns_glb = ns; + div1_glb = div1; + div2_glb = div2; + } + if (ns >= *nanosec && ns < ns_lub) { + ns_lub = ns; + div1_lub = div1; + div2_lub = div2; + } + } + } + + round_mode &= TRIG_ROUND_MASK; + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + ns_high = div1_lub * div2_lub * i8253_osc_base; + ns_low = div1_glb * div2_glb * i8253_osc_base; + if (ns_high - *nanosec < *nanosec - ns_low) { + div1 = div1_lub; + div2 = div2_lub; + } else { + div1 = div1_glb; + div2 = div2_glb; + } + break; + case TRIG_ROUND_UP: + div1 = div1_lub; + div2 = div2_lub; + break; + case TRIG_ROUND_DOWN: + div1 = div1_glb; + div2 = div2_glb; + break; + } + + *nanosec = div1 * div2 * i8253_osc_base; + /* masking is done since counter maps zero to 0x10000 */ + *d1 = div1 & 0xffff; + *d2 = div2 & 0xffff; + return; +} + +#ifndef CMDTEST +/* i8254_load programs 8254 counter chip. It should also work for the 8253. + * base_address is the lowest io address + * for the chip (the address of counter 0). + * counter_number is the counter you want to load (0,1 or 2) + * count is the number to load into the counter. + * + * You probably want to use mode 2. + * + * Use i8254_mm_load() if you board uses memory-mapped io, it is + * the same as i8254_load() except it uses writeb() instead of outb(). + * + * Neither i8254_load() or i8254_read() do their loading/reading + * atomically. The 16 bit read/writes are performed with two successive + * 8 bit read/writes. So if two parts of your driver do a load/read on + * the same counter, it may be necessary to protect these functions + * with a spinlock. + * + * FMH + */ + +#define i8254_control_reg 3 + +static inline int i8254_load(unsigned long base_address, unsigned int regshift, + unsigned int counter_number, unsigned int count, + unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (count > 0xffff) + return -1; + if (mode > 5) + return -1; + if ((mode == 2 || mode == 3) && count == 1) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= (mode << 1); /* set counter mode */ + outb(byte, base_address + (i8254_control_reg << regshift)); + byte = count & 0xff; /* lsb of counter value */ + outb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + outb(byte, base_address + (counter_number << regshift)); + + return 0; +} + +static inline int i8254_mm_load(void __iomem *base_address, + unsigned int regshift, + unsigned int counter_number, + unsigned int count, + unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (count > 0xffff) + return -1; + if (mode > 5) + return -1; + if ((mode == 2 || mode == 3) && count == 1) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= (mode << 1); /* set counter mode */ + writeb(byte, base_address + (i8254_control_reg << regshift)); + byte = count & 0xff; /* lsb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); + + return 0; +} + +/* Returns 16 bit counter value, should work for 8253 also.*/ +static inline int i8254_read(unsigned long base_address, unsigned int regshift, + unsigned int counter_number) +{ + unsigned int byte; + int ret; + + if (counter_number > 2) + return -1; + + /* latch counter */ + byte = counter_number << 6; + outb(byte, base_address + (i8254_control_reg << regshift)); + + /* read lsb */ + ret = inb(base_address + (counter_number << regshift)); + /* read msb */ + ret += inb(base_address + (counter_number << regshift)) << 8; + + return ret; +} + +static inline int i8254_mm_read(void __iomem *base_address, + unsigned int regshift, + unsigned int counter_number) +{ + unsigned int byte; + int ret; + + if (counter_number > 2) + return -1; + + /* latch counter */ + byte = counter_number << 6; + writeb(byte, base_address + (i8254_control_reg << regshift)); + + /* read lsb */ + ret = readb(base_address + (counter_number << regshift)); + /* read msb */ + ret += readb(base_address + (counter_number << regshift)) << 8; + + return ret; +} + +/* Loads 16 bit initial counter value, should work for 8253 also. */ +static inline void i8254_write(unsigned long base_address, + unsigned int regshift, + unsigned int counter_number, unsigned int count) +{ + unsigned int byte; + + if (counter_number > 2) + return; + + byte = count & 0xff; /* lsb of counter value */ + outb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + outb(byte, base_address + (counter_number << regshift)); +} + +static inline void i8254_mm_write(void __iomem *base_address, + unsigned int regshift, + unsigned int counter_number, + unsigned int count) +{ + unsigned int byte; + + if (counter_number > 2) + return; + + byte = count & 0xff; /* lsb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); +} + +/* Set counter mode, should work for 8253 also. + * Note: the 'mode' value is different to that for i8254_load() and comes + * from the INSN_CONFIG_8254_SET_MODE command: + * I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 + * OR'ed with: + * I8254_BCD, I8254_BINARY + */ +static inline int i8254_set_mode(unsigned long base_address, + unsigned int regshift, + unsigned int counter_number, unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (mode > (I8254_MODE5 | I8254_BCD)) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= mode; /* set counter mode and BCD|binary */ + outb(byte, base_address + (i8254_control_reg << regshift)); + + return 0; +} + +static inline int i8254_mm_set_mode(void __iomem *base_address, + unsigned int regshift, + unsigned int counter_number, + unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (mode > (I8254_MODE5 | I8254_BCD)) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= mode; /* set counter mode and BCD|binary */ + writeb(byte, base_address + (i8254_control_reg << regshift)); + + return 0; +} + +static inline int i8254_status(unsigned long base_address, + unsigned int regshift, + unsigned int counter_number) +{ + outb(0xE0 | (2 << counter_number), + base_address + (i8254_control_reg << regshift)); + return inb(base_address + (counter_number << regshift)); +} + +static inline int i8254_mm_status(void __iomem *base_address, + unsigned int regshift, + unsigned int counter_number) +{ + writeb(0xE0 | (2 << counter_number), + base_address + (i8254_control_reg << regshift)); + return readb(base_address + (counter_number << regshift)); +} + +#endif + +#endif diff --git a/drivers/staging/comedi/drivers/8255.c b/drivers/staging/comedi/drivers/8255.c new file mode 100644 index 00000000000..46113a37413 --- /dev/null +++ b/drivers/staging/comedi/drivers/8255.c @@ -0,0 +1,380 @@ +/* + 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'. + +*/ + +/* + This file contains an exported subdevice for driving an 8255. + + To use this subdevice as part of another driver, you need to + set up the subdevice in the attach function of the driver by + calling: + + subdev_8255_init(device, subdevice, io_function, iobase) + + device and subdevice are pointers to the device and subdevice + structures. io_function will be called to provide the + low-level input/output to the device, i.e., actual register + access. io_function will be called with the value of iobase + as the last parameter. If the 8255 device is mapped as 4 + consecutive I/O ports, you can use NULL for io_function + and the I/O port base for iobase, and an internal function will + handle the register access. + + In addition, if the main driver handles interrupts, you can + enable commands on the subdevice by calling subdev_8255_init_irq() + instead. Then, when you get an interrupt that is likely to be + from the 8255, you should call subdev_8255_interrupt(), which + will copy the latched value to a Comedi buffer. + */ + +#include <linux/module.h> +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "8255.h" + +#define _8255_SIZE 4 + +#define _8255_DATA 0 +#define _8255_CR 3 + +#define CR_C_LO_IO 0x01 +#define CR_B_IO 0x02 +#define CR_B_MODE 0x04 +#define CR_C_HI_IO 0x08 +#define CR_A_IO 0x10 +#define CR_A_MODE(a) ((a)<<5) +#define CR_CW 0x80 + +struct subdev_8255_private { + unsigned long iobase; + int (*io)(int, int, int, unsigned long); +}; + +static int subdev_8255_io(int dir, int port, int data, unsigned long iobase) +{ + if (dir) { + outb(data, iobase + port); + return 0; + } else { + return inb(iobase + port); + } +} + +void subdev_8255_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct subdev_8255_private *spriv = s->private; + unsigned long iobase = spriv->iobase; + unsigned short d; + + d = spriv->io(0, _8255_DATA, 0, iobase); + d |= (spriv->io(0, _8255_DATA + 1, 0, iobase) << 8); + + comedi_buf_put(s, d); + s->async->events |= COMEDI_CB_EOS; + + comedi_event(dev, s); +} +EXPORT_SYMBOL_GPL(subdev_8255_interrupt); + +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 iobase = spriv->iobase; + unsigned int mask; + unsigned int v; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0xff) + spriv->io(1, _8255_DATA, s->state & 0xff, iobase); + if (mask & 0xff00) + spriv->io(1, _8255_DATA + 1, (s->state >> 8) & 0xff, + iobase); + if (mask & 0xff0000) + spriv->io(1, _8255_DATA + 2, (s->state >> 16) & 0xff, + iobase); + } + + v = spriv->io(0, _8255_DATA, 0, iobase); + v |= (spriv->io(0, _8255_DATA + 1, 0, iobase) << 8); + v |= (spriv->io(0, _8255_DATA + 2, 0, iobase) << 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 iobase = spriv->iobase; + int config; + + config = CR_CW; + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= CR_A_IO; + if (!(s->io_bits & 0x00ff00)) + config |= CR_B_IO; + if (!(s->io_bits & 0x0f0000)) + config |= CR_C_LO_IO; + if (!(s->io_bits & 0xf00000)) + config |= CR_C_HI_IO; + + spriv->io(1, _8255_CR, config, iobase); +} + +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_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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4 */ + + if (err) + return 4; + + return 0; +} + +static int subdev_8255_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* FIXME */ + + return 0; +} + +static int subdev_8255_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* FIXME */ + + return 0; +} + +int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s, + int (*io)(int, int, int, unsigned long), + unsigned long iobase) +{ + struct subdev_8255_private *spriv; + + spriv = comedi_alloc_spriv(s, sizeof(*spriv)); + if (!spriv) + return -ENOMEM; + + spriv->iobase = iobase; + spriv->io = io ? io : subdev_8255_io; + + 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; +} +EXPORT_SYMBOL_GPL(subdev_8255_init); + +int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s, + int (*io)(int, int, int, unsigned long), + unsigned long iobase) +{ + int ret; + + ret = subdev_8255_init(dev, s, io, iobase); + if (ret) + return ret; + + s->len_chanlist = 1; + s->do_cmdtest = subdev_8255_cmdtest; + s->do_cmd = subdev_8255_cmd; + s->cancel = subdev_8255_cancel; + + return 0; +} +EXPORT_SYMBOL_GPL(subdev_8255_init_irq); + +/* + + Start of the 8255 standalone device + + */ + +static int dev_8255_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + unsigned long iobase; + 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]; + + ret = __comedi_request_region(dev, iobase, _8255_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->iobase, _8255_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 00000000000..795d232a6c0 --- /dev/null +++ b/drivers/staging/comedi/drivers/8255.h @@ -0,0 +1,33 @@ +/* + 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" + +int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s, + int (*io)(int, int, int, unsigned long), + unsigned long iobase); +int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s, + int (*io)(int, int, int, unsigned long), + unsigned long iobase); +void subdev_8255_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s); + +#endif diff --git a/drivers/staging/comedi/drivers/8255_pci.c b/drivers/staging/comedi/drivers/8255_pci.c new file mode 100644 index 00000000000..46a385c29ba --- /dev/null +++ b/drivers/staging/comedi/drivers/8255_pci.c @@ -0,0 +1,329 @@ +/* + * 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] - 24 channels + (ADLink) PCI-7248 [adl_pci-7248] - 48 channels + (ADLink) PCI-7296 [adl_pci-7296] - 96 channels + (Measurement Computing) PCI-DIO24 [cb_pci-dio24] - 24 channels + (Measurement Computing) PCI-DIO24H [cb_pci-dio24h] - 24 channels + (Measurement Computing) PCI-DIO48H [cb_pci-dio48h] - 48 channels + (Measurement Computing) PCI-DIO96H [cb_pci-dio96h] - 96 channels + (National Instruments) PCI-DIO-96 [ni_pci-dio-96] - 96 channels + (National Instruments) PCI-DIO-96B [ni_pci-dio-96b] - 96 channels + (National Instruments) PXI-6508 [ni_pxi-6508] - 96 channels + (National Instruments) PCI-6503 [ni_pci-6503] - 24 channels + (National Instruments) PCI-6503B [ni_pci-6503b] - 24 channels + (National Instruments) PCI-6503X [ni_pci-6503x] - 24 channels + (National Instruments) PXI-6503 [ni_pxi-6503] - 24 channels +Author: H Hartley Sweeten <hsweeten@visionengravers.com> +Updated: Wed, 12 Sep 2012 11:52:01 -0700 +Status: untested + +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 <linux/pci.h> + +#include "../comedidev.h" + +#include "8255.h" +#include "mite.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, + }, +}; + +struct pci_8255_private { + void __iomem *mmio_base; +}; + +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_mmio(int dir, int port, int data, unsigned long iobase) +{ + void __iomem *mmio_base = (void __iomem *)iobase; + + if (dir) { + writeb(data, mmio_base + port); + return 0; + } else { + return readb(mmio_base + port); + } +} + +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 pci_8255_private *devpriv; + struct comedi_subdevice *s; + bool is_mmio; + 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; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + if (board->has_mite) { + ret = pci_8255_mite_init(pcidev); + if (ret) + return ret; + } + + is_mmio = (pci_resource_flags(pcidev, board->dio_badr) & + IORESOURCE_MEM) != 0; + if (is_mmio) { + devpriv->mmio_base = pci_ioremap_bar(pcidev, board->dio_badr); + if (!devpriv->mmio_base) + 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++) { + unsigned long iobase; + + s = &dev->subdevices[i]; + if (is_mmio) { + iobase = (unsigned long)(devpriv->mmio_base + (i * 4)); + ret = subdev_8255_init(dev, s, pci_8255_mmio, iobase); + } else { + iobase = dev->iobase + (i * 4); + ret = subdev_8255_init(dev, s, NULL, iobase); + } + if (ret) + return ret; + } + + return 0; +} + +static void pci_8255_detach(struct comedi_device *dev) +{ + struct pci_8255_private *devpriv = dev->private; + + if (devpriv && devpriv->mmio_base) + iounmap(devpriv->mmio_base); + comedi_pci_disable(dev); +} + +static struct comedi_driver pci_8255_driver = { + .driver_name = "8255_pci", + .module = THIS_MODULE, + .auto_attach = pci_8255_auto_attach, + .detach = pci_8255_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 00000000000..0757a82ddcf --- /dev/null +++ b/drivers/staging/comedi/drivers/Makefile @@ -0,0 +1,142 @@ +# Makefile for individual comedi drivers +# +ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG + +# Comedi "helper" modules + +# 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 +obj-$(CONFIG_COMEDI_SKEL) += skel.o + +# Comedi ISA drivers +obj-$(CONFIG_COMEDI_AMPLC_DIO200_ISA) += amplc_dio200.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_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_035) += addi_apci_035.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) += amplc_pc236.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_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.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_DAS08) += das08.o +obj-$(CONFIG_COMEDI_FC) += comedi_fc.o diff --git a/drivers/staging/comedi/drivers/addi-data/addi_common.c b/drivers/staging/comedi/drivers/addi-data/addi_common.c new file mode 100644 index 00000000000..dc87df03220 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_common.c @@ -0,0 +1,284 @@ +/** +@verbatim + +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. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstrasse 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : ADDI DATA | Compiler : GCC | + | Modulname : addi_common.c | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Author : | Date : | + +-----------------------------------------------------------------------+ + | Description : ADDI COMMON Main Module | + +-----------------------------------------------------------------------+ +*/ + +static int i_ADDIDATA_InsnReadEeprom(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + unsigned short w_Address = CR_CHAN(insn->chanspec); + unsigned short w_Data; + + w_Data = addi_eeprom_readw(devpriv->i_IobaseAmcc, + this_board->pc_EepromChip, 2 * w_Address); + data[0] = w_Data; + + return insn->n; +} + +static irqreturn_t v_ADDI_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + const struct addi_board *this_board = comedi_board(dev); + + this_board->interrupt(irq, d); + return IRQ_RETVAL(1); +} + +static int i_ADDI_Reset(struct comedi_device *dev) +{ + const struct addi_board *this_board = comedi_board(dev); + + this_board->reset(dev); + return 0; +} + +static int addi_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv; + struct comedi_subdevice *s; + int ret, n_subdevices; + unsigned int dw_Dummy; + + dev->board_name = this_board->pc_DriverName; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + if (this_board->i_IorangeBase1) + dev->iobase = pci_resource_start(pcidev, 1); + else + dev->iobase = pci_resource_start(pcidev, 0); + + devpriv->iobase = dev->iobase; + devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0); + devpriv->i_IobaseAddon = pci_resource_start(pcidev, 2); + devpriv->i_IobaseReserved = pci_resource_start(pcidev, 3); + + /* Initialize parameters that can be overridden in EEPROM */ + devpriv->s_EeParameters.i_NbrAiChannel = this_board->i_NbrAiChannel; + devpriv->s_EeParameters.i_NbrAoChannel = this_board->i_NbrAoChannel; + devpriv->s_EeParameters.i_AiMaxdata = this_board->i_AiMaxdata; + devpriv->s_EeParameters.i_AoMaxdata = this_board->i_AoMaxdata; + devpriv->s_EeParameters.i_NbrDiChannel = this_board->i_NbrDiChannel; + devpriv->s_EeParameters.i_NbrDoChannel = this_board->i_NbrDoChannel; + devpriv->s_EeParameters.i_DoMaxdata = this_board->i_DoMaxdata; + devpriv->s_EeParameters.i_Timer = this_board->i_Timer; + devpriv->s_EeParameters.ui_MinAcquisitiontimeNs = + this_board->ui_MinAcquisitiontimeNs; + devpriv->s_EeParameters.ui_MinDelaytimeNs = + this_board->ui_MinDelaytimeNs; + + /* ## */ + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, v_ADDI_Interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + /* Read eepeom and fill addi_board Structure */ + + if (this_board->i_PCIEeprom) { + if (!(strcmp(this_board->pc_EepromChip, "S5920"))) { + /* Set 3 wait stait */ + if (!(strcmp(dev->board_name, "apci035"))) + outl(0x80808082, devpriv->i_IobaseAmcc + 0x60); + else + outl(0x83838383, devpriv->i_IobaseAmcc + 0x60); + + /* Enable the interrupt for the controller */ + dw_Dummy = inl(devpriv->i_IobaseAmcc + 0x38); + outl(dw_Dummy | 0x2000, devpriv->i_IobaseAmcc + 0x38); + } + addi_eeprom_read_info(dev, pci_resource_start(pcidev, 0)); + } + + n_subdevices = 7; + ret = comedi_alloc_subdevices(dev, n_subdevices); + if (ret) + return ret; + + /* Allocate and Initialise AI Subdevice Structures */ + s = &dev->subdevices[0]; + if ((devpriv->s_EeParameters.i_NbrAiChannel) + || (this_board->i_NbrAiChannelDiff)) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_COMMON | SDF_GROUND + | SDF_DIFF; + if (devpriv->s_EeParameters.i_NbrAiChannel) { + s->n_chan = + devpriv->s_EeParameters.i_NbrAiChannel; + devpriv->b_SingelDiff = 0; + } else { + s->n_chan = this_board->i_NbrAiChannelDiff; + devpriv->b_SingelDiff = 1; + } + s->maxdata = devpriv->s_EeParameters.i_AiMaxdata; + s->len_chanlist = this_board->i_AiChannelList; + s->range_table = this_board->pr_AiRangelist; + + s->insn_config = this_board->ai_config; + s->insn_read = this_board->ai_read; + s->insn_write = this_board->ai_write; + s->insn_bits = this_board->ai_bits; + s->do_cmdtest = this_board->ai_cmdtest; + s->do_cmd = this_board->ai_cmd; + s->cancel = this_board->ai_cancel; + + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Allocate and Initialise AO Subdevice Structures */ + s = &dev->subdevices[1]; + if (devpriv->s_EeParameters.i_NbrAoChannel) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = devpriv->s_EeParameters.i_NbrAoChannel; + s->maxdata = devpriv->s_EeParameters.i_AoMaxdata; + s->len_chanlist = + devpriv->s_EeParameters.i_NbrAoChannel; + s->insn_write = this_board->ao_write; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + /* Allocate and Initialise DI Subdevice Structures */ + s = &dev->subdevices[2]; + if (devpriv->s_EeParameters.i_NbrDiChannel) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = devpriv->s_EeParameters.i_NbrDiChannel; + s->maxdata = 1; + s->len_chanlist = + devpriv->s_EeParameters.i_NbrDiChannel; + s->range_table = &range_digital; + s->insn_config = this_board->di_config; + s->insn_read = this_board->di_read; + s->insn_write = this_board->di_write; + s->insn_bits = this_board->di_bits; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + /* Allocate and Initialise DO Subdevice Structures */ + s = &dev->subdevices[3]; + if (devpriv->s_EeParameters.i_NbrDoChannel) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = + SDF_READABLE | SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = devpriv->s_EeParameters.i_NbrDoChannel; + s->maxdata = devpriv->s_EeParameters.i_DoMaxdata; + s->len_chanlist = + devpriv->s_EeParameters.i_NbrDoChannel; + s->range_table = &range_digital; + + /* insn_config - for digital output memory */ + s->insn_config = this_board->do_config; + s->insn_write = this_board->do_write; + s->insn_bits = this_board->do_bits; + s->insn_read = this_board->do_read; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Allocate and Initialise Timer Subdevice Structures */ + s = &dev->subdevices[4]; + if (devpriv->s_EeParameters.i_Timer) { + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 1; + s->maxdata = 0; + s->len_chanlist = 1; + s->range_table = &range_digital; + + s->insn_write = this_board->timer_write; + s->insn_read = this_board->timer_read; + s->insn_config = this_board->timer_config; + s->insn_bits = this_board->timer_bits; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Allocate and Initialise TTL */ + s = &dev->subdevices[5]; + s->type = COMEDI_SUBD_UNUSED; + + /* EEPROM */ + s = &dev->subdevices[6]; + if (this_board->i_PCIEeprom) { + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->n_chan = 256; + s->maxdata = 0xffff; + s->insn_read = i_ADDIDATA_InsnReadEeprom; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + i_ADDI_Reset(dev); + return 0; +} + +static void i_ADDI_Detach(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + + if (devpriv) { + if (dev->iobase) + i_ADDI_Reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + } + comedi_pci_disable(dev); +} diff --git a/drivers/staging/comedi/drivers/addi-data/addi_common.h b/drivers/staging/comedi/drivers/addi-data/addi_common.h new file mode 100644 index 00000000000..5c6a11c35de --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_common.h @@ -0,0 +1,165 @@ +/* + * 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. + */ + +#include <linux/sched.h> +#include <linux/interrupt.h> + +#define LOWORD(W) (unsigned short)((W) & 0xFFFF) +#define HIWORD(W) (unsigned short)(((W) >> 16) & 0xFFFF) + +#define ADDI_ENABLE 1 +#define ADDI_DISABLE 0 +#define APCI1710_SAVE_INTERRUPT 1 + +#define ADDIDATA_EEPROM 1 +#define ADDIDATA_NO_EEPROM 0 +#define ADDIDATA_93C76 "93C76" +#define ADDIDATA_S5920 "S5920" + +/* ADDIDATA Enable Disable */ +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 + +/* Structures */ + +/* structure for the boardtype */ +struct addi_board { + const char *pc_DriverName; /* driver name */ + int i_IorangeBase1; + int i_PCIEeprom; /* eeprom present or not */ + char *pc_EepromChip; /* type of chip */ + int i_NbrAiChannel; /* num of A/D chans */ + int i_NbrAiChannelDiff; /* num of A/D chans in diff mode */ + int i_AiChannelList; /* len of chanlist */ + int i_NbrAoChannel; /* num of D/A chans */ + int i_AiMaxdata; /* resolution of A/D */ + int i_AoMaxdata; /* resolution of D/A */ + const struct comedi_lrange *pr_AiRangelist; /* rangelist for A/D */ + + int i_NbrDiChannel; /* Number of DI channels */ + int i_NbrDoChannel; /* Number of DO channels */ + int i_DoMaxdata; /* data to set all channels high */ + + int i_Timer; /* timer subdevice present or not */ + unsigned int ui_MinAcquisitiontimeNs; /* Minimum Acquisition in Nano secs */ + unsigned int ui_MinDelaytimeNs; /* Minimum Delay in Nano secs */ + + /* interrupt and reset */ + void (*interrupt)(int irq, void *d); + int (*reset)(struct comedi_device *); + + /* Subdevice functions */ + + /* ANALOG INPUT */ + int (*ai_config)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*ai_read)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*ai_write)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*ai_bits)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*ai_cmdtest)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_cmd *); + int (*ai_cmd)(struct comedi_device *, struct comedi_subdevice *); + int (*ai_cancel)(struct comedi_device *, struct comedi_subdevice *); + + /* Analog Output */ + int (*ao_write)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + + /* Digital Input */ + int (*di_config)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*di_read)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*di_write)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*di_bits)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + + /* Digital Output */ + int (*do_config)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*do_write)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*do_bits)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*do_read)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + + /* TIMER */ + int (*timer_config)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*timer_write)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*timer_read)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*timer_bits)(struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); +}; + +struct addi_private { + int iobase; + int i_IobaseAmcc; /* base+size for AMCC chip */ + int i_IobaseAddon; /* addon base address */ + int i_IobaseReserved; + unsigned int ui_AiActualScan; /* how many scans we finished */ + unsigned int ui_AiNbrofChannels; /* how many channels is measured */ + unsigned int ui_AiChannelList[32]; /* actual chanlist */ + unsigned int ui_AiReadData[32]; + unsigned short us_UseDma; /* To use Dma or not */ + unsigned char b_DmaDoubleBuffer; /* we can use double buffering */ + unsigned int ui_DmaActualBuffer; /* which buffer is used now */ + unsigned short *ul_DmaBufferVirtual[2]; /* pointers to DMA buffer */ + unsigned int ul_DmaBufferHw[2]; /* hw address of DMA buff */ + unsigned int ui_DmaBufferSize[2]; /* size of dma buffer in bytes */ + unsigned int ui_DmaBufferUsesize[2]; /* which size we may now used for transfer */ + unsigned int ui_DmaBufferPages[2]; /* number of pages in buffer */ + unsigned char b_DigitalOutputRegister; /* Digital Output Register */ + unsigned char b_OutputMemoryStatus; + unsigned char b_TimerSelectMode; /* Contain data written at iobase + 0C */ + unsigned char b_ModeSelectRegister; /* Contain data written at iobase + 0E */ + unsigned short us_OutputRegister; /* Contain data written at iobase + 0 */ + unsigned char b_Timer2Mode; /* Specify the timer 2 mode */ + unsigned char b_Timer2Interrupt; /* Timer2 interrupt enable or disable */ + unsigned int ai_running:1; + unsigned char b_InterruptMode; /* eoc eos or dma */ + unsigned char b_EocEosInterrupt; /* Enable disable eoc eos interrupt */ + unsigned int ui_EocEosConversionTime; + unsigned char b_SingelDiff; + unsigned char b_ExttrigEnable; /* To enable or disable external trigger */ + + /* Pointer to the current process */ + struct task_struct *tsk_Current; + + /* Parameters read from EEPROM overriding static board info */ + struct { + int i_NbrAiChannel; /* num of A/D chans */ + int i_NbrAoChannel; /* num of D/A chans */ + int i_AiMaxdata; /* resolution of A/D */ + int i_AoMaxdata; /* resolution of D/A */ + int i_NbrDiChannel; /* Number of DI channels */ + int i_NbrDoChannel; /* Number of DO channels */ + int i_DoMaxdata; /* data to set all channels high */ + int i_Timer; /* timer subdevice present or not */ + unsigned int ui_MinAcquisitiontimeNs; + /* Minimum Acquisition in Nano secs */ + unsigned int ui_MinDelaytimeNs; + /* Minimum Delay in Nano secs */ + } s_EeParameters; +}; diff --git a/drivers/staging/comedi/drivers/addi-data/addi_eeprom.c b/drivers/staging/comedi/drivers/addi-data/addi_eeprom.c new file mode 100644 index 00000000000..aafc172f3a9 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_eeprom.c @@ -0,0 +1,360 @@ +/* + * addi_eeprom.c - ADDI EEPROM Module + * 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/delay.h> + +#define NVRAM_USER_DATA_START 0x100 + +#define NVCMD_BEGIN_READ (0x7 << 5) /* nvRam begin read command */ +#define NVCMD_LOAD_LOW (0x4 << 5) /* nvRam load low command */ +#define NVCMD_LOAD_HIGH (0x5 << 5) /* nvRam load high command */ + +#define EE93C76_CLK_BIT (1 << 0) +#define EE93C76_CS_BIT (1 << 1) +#define EE93C76_DOUT_BIT (1 << 2) +#define EE93C76_DIN_BIT (1 << 3) +#define EE93C76_READ_CMD (0x0180 << 4) +#define EE93C76_CMD_LEN 13 + +#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 + +static void addi_eeprom_clk_93c76(unsigned long iobase, unsigned int val) +{ + outl(val & ~EE93C76_CLK_BIT, iobase); + udelay(100); + + outl(val | EE93C76_CLK_BIT, iobase); + udelay(100); +} + +static unsigned int addi_eeprom_cmd_93c76(unsigned long iobase, + unsigned int cmd, + unsigned char len) +{ + unsigned int val = EE93C76_CS_BIT; + int i; + + /* Toggle EEPROM's Chip select to get it out of Shift Register Mode */ + outl(val, iobase); + udelay(100); + + /* Send EEPROM command - one bit at a time */ + for (i = (len - 1); i >= 0; i--) { + if (cmd & (1 << i)) + val |= EE93C76_DOUT_BIT; + else + val &= ~EE93C76_DOUT_BIT; + + /* Write the command */ + outl(val, iobase); + udelay(100); + + addi_eeprom_clk_93c76(iobase, val); + } + return val; +} + +static unsigned short addi_eeprom_readw_93c76(unsigned long iobase, + unsigned short addr) +{ + unsigned short val = 0; + unsigned int cmd; + unsigned int tmp; + int i; + + /* Send EEPROM read command and offset to EEPROM */ + cmd = EE93C76_READ_CMD | (addr / 2); + cmd = addi_eeprom_cmd_93c76(iobase, cmd, EE93C76_CMD_LEN); + + /* Get the 16-bit value */ + for (i = 0; i < 16; i++) { + addi_eeprom_clk_93c76(iobase, cmd); + + tmp = inl(iobase); + udelay(100); + + val <<= 1; + if (tmp & EE93C76_DIN_BIT) + val |= 0x1; + } + + /* Toggle EEPROM's Chip select to get it out of Shift Register Mode */ + outl(0, iobase); + udelay(100); + + return val; +} + +static void addi_eeprom_nvram_wait(unsigned long iobase) +{ + unsigned char val; + + do { + val = inb(iobase + AMCC_OP_REG_MCSR_NVCMD); + } while (val & 0x80); +} + +static unsigned short addi_eeprom_readw_nvram(unsigned long iobase, + unsigned short addr) +{ + unsigned short val = 0; + unsigned char tmp; + unsigned char i; + + for (i = 0; i < 2; i++) { + /* Load the low 8 bit address */ + outb(NVCMD_LOAD_LOW, iobase + AMCC_OP_REG_MCSR_NVCMD); + addi_eeprom_nvram_wait(iobase); + outb((addr + i) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA); + addi_eeprom_nvram_wait(iobase); + + /* Load the high 8 bit address */ + outb(NVCMD_LOAD_HIGH, iobase + AMCC_OP_REG_MCSR_NVCMD); + addi_eeprom_nvram_wait(iobase); + outb(((addr + i) >> 8) & 0xff, + iobase + AMCC_OP_REG_MCSR_NVDATA); + addi_eeprom_nvram_wait(iobase); + + /* Read the eeprom data byte */ + outb(NVCMD_BEGIN_READ, iobase + AMCC_OP_REG_MCSR_NVCMD); + addi_eeprom_nvram_wait(iobase); + tmp = inb(iobase + AMCC_OP_REG_MCSR_NVDATA); + addi_eeprom_nvram_wait(iobase); + + if (i == 0) + val |= tmp; + else + val |= (tmp << 8); + } + + return val; +} + +static unsigned short addi_eeprom_readw(unsigned long iobase, + char *type, + unsigned short addr) +{ + unsigned short val = 0; + + /* Add the offset to the start of the user data */ + addr += NVRAM_USER_DATA_START; + + if (!strcmp(type, "S5920") || !strcmp(type, "S5933")) + val = addi_eeprom_readw_nvram(iobase, addr); + + if (!strcmp(type, "93C76")) + val = addi_eeprom_readw_93c76(iobase, addr); + + return val; +} + +static void addi_eeprom_read_di_info(struct comedi_device *dev, + unsigned long iobase, + unsigned short addr) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + char *type = this_board->pc_EepromChip; + unsigned short tmp; + + /* Number of channels */ + tmp = addi_eeprom_readw(iobase, type, addr + 6); + devpriv->s_EeParameters.i_NbrDiChannel = tmp; + + /* Interruptible or not */ + tmp = addi_eeprom_readw(iobase, type, addr + 8); + tmp = (tmp >> 7) & 0x01; + + /* How many interruptible logic */ + tmp = addi_eeprom_readw(iobase, type, addr + 10); +} + +static void addi_eeprom_read_do_info(struct comedi_device *dev, + unsigned long iobase, + unsigned short addr) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + char *type = this_board->pc_EepromChip; + unsigned short tmp; + + /* Number of channels */ + tmp = addi_eeprom_readw(iobase, type, addr + 6); + devpriv->s_EeParameters.i_NbrDoChannel = tmp; + + devpriv->s_EeParameters.i_DoMaxdata = 0xffffffff >> (32 - tmp); +} + +static void addi_eeprom_read_timer_info(struct comedi_device *dev, + unsigned long iobase, + unsigned short addr) +{ + struct addi_private *devpriv = dev->private; +#if 0 + const struct addi_board *this_board = comedi_board(dev); + char *type = this_board->pc_EepromChip; + unsigned short offset = 0; + unsigned short ntimers; + unsigned short tmp; + int i; + + /* Number of Timers */ + ntimers = addi_eeprom_readw(iobase, type, addr + 6); + + /* Read header size */ + for (i = 0; i < ntimers; i++) { + unsigned short size; + unsigned short res; + unsigned short mode; + unsigned short min_timing; + unsigned short timebase; + + size = addi_eeprom_readw(iobase, type, addr + 8 + offset + 0); + + /* Resolution / Mode */ + tmp = addi_eeprom_readw(iobase, type, addr + 8 + offset + 2); + res = (tmp >> 10) & 0x3f; + mode = (tmp >> 4) & 0x3f; + + /* MinTiming / Timebase */ + tmp = addi_eeprom_readw(iobase, type, addr + 8 + offset + 4); + min_timing = (tmp >> 6) & 0x3ff; + Timebase = tmp & 0x3f; + + offset += size; + } +#endif + /* Timer subdevice present */ + devpriv->s_EeParameters.i_Timer = 1; +} + +static void addi_eeprom_read_ao_info(struct comedi_device *dev, + unsigned long iobase, + unsigned short addr) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + char *type = this_board->pc_EepromChip; + unsigned short tmp; + + /* No of channels for 1st hard component */ + tmp = addi_eeprom_readw(iobase, type, addr + 10); + devpriv->s_EeParameters.i_NbrAoChannel = (tmp >> 4) & 0x3ff; + + /* Resolution for 1st hard component */ + tmp = addi_eeprom_readw(iobase, type, addr + 16); + tmp = (tmp >> 8) & 0xff; + devpriv->s_EeParameters.i_AoMaxdata = 0xfff >> (16 - tmp); +} + +static void addi_eeprom_read_ai_info(struct comedi_device *dev, + unsigned long iobase, + unsigned short addr) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + char *type = this_board->pc_EepromChip; + unsigned short offset; + unsigned short tmp; + + /* No of channels for 1st hard component */ + tmp = addi_eeprom_readw(iobase, type, addr + 10); + devpriv->s_EeParameters.i_NbrAiChannel = (tmp >> 4) & 0x3ff; + if (!strcmp(this_board->pc_DriverName, "apci3200")) + devpriv->s_EeParameters.i_NbrAiChannel *= 4; + + tmp = addi_eeprom_readw(iobase, type, addr + 16); + devpriv->s_EeParameters.ui_MinAcquisitiontimeNs = tmp * 1000; + + tmp = addi_eeprom_readw(iobase, type, addr + 30); + devpriv->s_EeParameters.ui_MinDelaytimeNs = tmp * 1000; + + tmp = addi_eeprom_readw(iobase, type, addr + 20); + /* dma = (tmp >> 13) & 0x01; */ + + tmp = addi_eeprom_readw(iobase, type, addr + 72) & 0xff; + if (tmp) { /* > 0 */ + /* offset of first analog input single header */ + offset = 74 + (2 * tmp) + (10 * (1 + (tmp / 16))); + } else { /* = 0 */ + offset = 74; + } + + /* Resolution */ + tmp = addi_eeprom_readw(iobase, type, addr + offset + 2) & 0x1f; + devpriv->s_EeParameters.i_AiMaxdata = 0xffff >> (16 - tmp); +} + +static void addi_eeprom_read_info(struct comedi_device *dev, + unsigned long iobase) +{ + const struct addi_board *this_board = comedi_board(dev); + char *type = this_board->pc_EepromChip; + unsigned short size; + unsigned char nfuncs; + int i; + + size = addi_eeprom_readw(iobase, type, 8); + nfuncs = addi_eeprom_readw(iobase, type, 10) & 0xff; + + /* Read functionality details */ + for (i = 0; i < nfuncs; i++) { + unsigned short offset = i * 4; + unsigned short addr; + unsigned char func; + + func = addi_eeprom_readw(iobase, type, 12 + offset) & 0x3f; + addr = addi_eeprom_readw(iobase, type, 14 + offset); + + switch (func) { + case EEPROM_DIGITALINPUT: + addi_eeprom_read_di_info(dev, iobase, addr); + break; + + case EEPROM_DIGITALOUTPUT: + addi_eeprom_read_do_info(dev, iobase, addr); + break; + + case EEPROM_ANALOGINPUT: + addi_eeprom_read_ai_info(dev, iobase, addr); + break; + + case EEPROM_ANALOGOUTPUT: + addi_eeprom_read_ao_info(dev, iobase, addr); + break; + + case EEPROM_TIMER: + case EEPROM_WATCHDOG: + case EEPROM_TIMER_WATCHDOG_COUNTER: + addi_eeprom_read_timer_info(dev, iobase, addr); + break; + } + } +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c new file mode 100644 index 00000000000..28450f65a13 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c @@ -0,0 +1,485 @@ +/* + * 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. + */ + +/* Card Specific information */ +#define APCI035_ADDRESS_RANGE 255 + +/* Timer / Watchdog Related Defines */ +#define APCI035_TCW_SYNC_ENABLEDISABLE 0 +#define APCI035_TCW_RELOAD_VALUE 4 +#define APCI035_TCW_TIMEBASE 8 +#define APCI035_TCW_PROG 12 +#define APCI035_TCW_TRIG_STATUS 16 +#define APCI035_TCW_IRQ 20 +#define APCI035_TCW_WARN_TIMEVAL 24 +#define APCI035_TCW_WARN_TIMEBASE 28 + +#define ADDIDATA_TIMER 0 +/* #define ADDIDATA_WATCHDOG 1 */ + +#define APCI035_TW1 0 +#define APCI035_TW2 32 +#define APCI035_TW3 64 +#define APCI035_TW4 96 + +#define APCI035_AI_OFFSET 0 +#define APCI035_TEMP 128 +#define APCI035_ALR_SEQ 4 +#define APCI035_START_STOP_INDEX 8 +#define APCI035_ALR_START_STOP 12 +#define APCI035_ALR_IRQ 16 +#define APCI035_EOS 20 +#define APCI035_CHAN_NO 24 +#define APCI035_CHAN_VAL 28 +#define APCI035_CONV_TIME_TIME_BASE 36 +#define APCI035_RELOAD_CONV_TIME_VAL 32 +#define APCI035_DELAY_TIME_TIME_BASE 44 +#define APCI035_RELOAD_DELAY_TIME_VAL 40 +#define ENABLE_EXT_TRIG 1 +#define ENABLE_EXT_GATE 2 +#define ENABLE_EXT_TRIG_GATE 3 + +#define ANALOG_INPUT 0 +#define TEMPERATURE 1 +#define RESISTANCE 2 + +#define ADDIDATA_GREATER_THAN_TEST 0 +#define ADDIDATA_LESS_THAN_TEST 1 + +#define APCI035_MAXVOLT 2.5 + +#define ADDIDATA_UNIPOLAR 1 +#define ADDIDATA_BIPOLAR 2 + +/* ANALOG INPUT RANGE */ +static struct comedi_lrange range_apci035_ai = { + 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 int i_WatchdogNbr; +static int i_Temp; +static int i_Flag = 1; + +/* + * Configures The Timer , Counter or Watchdog + * + * data[0] 0 = Configure As Timer, 1 = Configure As Watchdog + * data[1] Watchdog number + * data[2] Time base Unit + * data[3] Reload Value + * data[4] External Trigger, 1 = Enable, 0 = Disable + * data[5] External Trigger Level + * 00 = Trigger Disabled + * 01 = Trigger Enabled (Low level) + * 10 = Trigger Enabled (High Level) + * 11 = Trigger Enabled (High/Low level) + * data[6] External Gate, 1 = Enable, 0 = Disable + * data[7] External Gate level + * 00 = Gate Disabled + * 01 = Gate Enabled (Low level) + * 10 = Gate Enabled (High Level) + * data[8] Warning Relay, 1 = Enable, 0 = Disable + * data[9] Warning Delay available + * data[10] Warning Relay Time unit + * data[11] Warning Relay Time Reload value + * data[12] Reset Relay, 1 = Enable, 0 = Disable + * data[13] Interrupt, 1 = Enable, 0 = Disable + */ +static int apci035_timer_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Status; + unsigned int ui_Command; + unsigned int ui_Mode; + + i_Temp = 0; + devpriv->tsk_Current = current; + devpriv->b_TimerSelectMode = data[0]; + i_WatchdogNbr = data[1]; + if (data[0] == 0) + ui_Mode = 2; + else + ui_Mode = 0; + + ui_Command = 0; + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Set the reload value */ + outl(data[3], devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 4); + + /* Set the time unit */ + outl(data[2], devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 8); + if (data[0] == ADDIDATA_TIMER) { + + /* Set the mode : */ + /* - Disable the hardware */ + /* - Disable the counter mode */ + /* - Disable the warning */ + /* - Disable the reset */ + /* - Enable the timer mode */ + /* - Set the timer mode */ + + ui_Command = + (ui_Command & 0xFFF719E2UL) | ui_Mode << 13UL | 0x10UL; + + } else if (data[0] == ADDIDATA_WATCHDOG) { + + /* Set the mode : */ + /* - Disable the hardware */ + /* - Disable the counter mode */ + /* - Disable the warning */ + /* - Disable the reset */ + /* - Disable the timer mode */ + + ui_Command = ui_Command & 0xFFF819E2UL; + + } else { + dev_err(dev->class_dev, "The parameter for Timer/watchdog selection is in error\n"); + return -EINVAL; + } + + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Disable the hardware trigger */ + ui_Command = ui_Command & 0xFFFFF89FUL; + if (data[4] == ADDIDATA_ENABLE) { + + /* Set the hardware trigger level */ + ui_Command = ui_Command | (data[5] << 5); + } + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Disable the hardware gate */ + ui_Command = ui_Command & 0xFFFFF87FUL; + if (data[6] == ADDIDATA_ENABLE) { + + /* Set the hardware gate level */ + ui_Command = ui_Command | (data[7] << 7); + } + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Disable the hardware output */ + ui_Command = ui_Command & 0xFFFFF9FBUL; + + /* Set the hardware output level */ + ui_Command = ui_Command | (data[8] << 2); + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + if (data[9] == ADDIDATA_ENABLE) { + + /* Set the reload value */ + outl(data[11], + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 24); + + /* Set the time unite */ + outl(data[10], + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 28); + } + + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Disable the hardware output */ + ui_Command = ui_Command & 0xFFFFF9F7UL; + + /* Set the hardware output level */ + ui_Command = ui_Command | (data[12] << 3); + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Enable the watchdog interrupt */ + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Set the interrupt selection */ + ui_Status = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 16); + + ui_Command = (ui_Command & 0xFFFFF9FDUL) | (data[13] << 1); + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + return insn->n; +} + +/* + * Start / Stop The Selected Timer , or Watchdog + * + * data[0] + * 0 - Stop Selected Timer/Watchdog + * 1 - Start Selected Timer/Watch*dog + * 2 - Trigger Selected Timer/Watchdog + * 3 - Stop All Timer/Watchdog + * 4 - Start All Timer/Watchdog + * 5 - Trigger All Timer/Watchdog + */ +static int apci035_timer_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Command; + int i_Count; + + if (data[0] == 1) { + ui_Command = + inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Start the hardware */ + ui_Command = (ui_Command & 0xFFFFF9FFUL) | 0x1UL; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + } + if (data[0] == 2) { + ui_Command = + inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + /* Set the trigger command */ + ui_Command = (ui_Command & 0xFFFFF9FFUL) | 0x200UL; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + } + + if (data[0] == 0) { + /* Stop The Watchdog */ + ui_Command = 0; + /* + * ui_Command = inl(devpriv->iobase+((i_WatchdogNbr-1)*32)+12); + * ui_Command = ui_Command & 0xFFFFF9FEUL; + */ + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + } + if (data[0] == 3) { + /* stop all Watchdogs */ + ui_Command = 0; + for (i_Count = 1; i_Count <= 4; i_Count++) { + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) + ui_Command = 0x2UL; + else + ui_Command = 0x10UL; + + i_WatchdogNbr = i_Count; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + + 0); + } + + } + if (data[0] == 4) { + /* start all Watchdogs */ + ui_Command = 0; + for (i_Count = 1; i_Count <= 4; i_Count++) { + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) + ui_Command = 0x1UL; + else + ui_Command = 0x8UL; + + i_WatchdogNbr = i_Count; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + + 0); + } + } + if (data[0] == 5) { + /* trigger all Watchdogs */ + ui_Command = 0; + for (i_Count = 1; i_Count <= 4; i_Count++) { + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) + ui_Command = 0x4UL; + else + ui_Command = 0x20UL; + + i_WatchdogNbr = i_Count; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + + 0); + } + i_Temp = 1; + } + return insn->n; +} + +/* + * Read The Selected Timer , Counter or Watchdog + * + * data[0] software trigger status + * data[1] hardware trigger status + * data[2] Software clear status + * data[3] Overflow status + * data[4] Timer actual value + */ +static int apci035_timer_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Status; /* Status register */ + + i_WatchdogNbr = insn->unused[0]; + + /* Get the status */ + ui_Status = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 16); + + /* Get the software trigger status */ + data[0] = ((ui_Status >> 1) & 1); + + /* Get the hardware trigger status */ + data[1] = ((ui_Status >> 2) & 1); + + /* Get the software clear status */ + data[2] = ((ui_Status >> 3) & 1); + + /* Get the overflow status */ + data[3] = ((ui_Status >> 0) & 1); + if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) + data[4] = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 0); + + return insn->n; +} + +/* + * Configures The Analog Input Subdevice + * + * data[0] Warning delay value + */ +static int apci035_ai_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + + devpriv->tsk_Current = current; + outl(0x200 | 0, devpriv->iobase + 128 + 0x4); + outl(0, devpriv->iobase + 128 + 0); + + /* Initialise the warning value */ + outl(0x300 | 0, devpriv->iobase + 128 + 0x4); + outl((data[0] << 8), devpriv->iobase + 128 + 0); + outl(0x200000UL, devpriv->iobase + 128 + 12); + + return insn->n; +} + +/* + * Read value of the selected channel + * + * data[0] Digital Value Of Input + */ +static int apci035_ai_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_CommandRegister; + + /* Set the start */ + ui_CommandRegister = 0x80000; + + /* Write the command register */ + outl(ui_CommandRegister, devpriv->iobase + 128 + 8); + + /* Read the digital value of the input */ + data[0] = inl(devpriv->iobase + 128 + 28); + return insn->n; +} + +static int apci035_reset(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + int i_Count; + + for (i_Count = 1; i_Count <= 4; i_Count++) { + i_WatchdogNbr = i_Count; + + /* stop all timers */ + outl(0x0, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 0); + } + outl(0x0, devpriv->iobase + 128 + 12); /* Disable the warning delay */ + + return 0; +} + +static void apci035_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct addi_private *devpriv = dev->private; + unsigned int ui_StatusRegister1; + unsigned int ui_StatusRegister2; + unsigned int ui_ReadCommand; + unsigned int ui_ChannelNumber; + unsigned int ui_DigitalTemperature; + + if (i_Temp == 1) { + i_WatchdogNbr = i_Flag; + i_Flag = i_Flag + 1; + } + + /* Read the interrupt status register of temperature Warning */ + ui_StatusRegister1 = inl(devpriv->iobase + 128 + 16); + + /* Read the interrupt status register for Watchdog/timer */ + ui_StatusRegister2 = + inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 20); + + /* Test if warning relay interrupt */ + if ((((ui_StatusRegister1) & 0x8) == 0x8)) { + + /* Disable the temperature warning */ + ui_ReadCommand = inl(devpriv->iobase + 128 + 12); + ui_ReadCommand = ui_ReadCommand & 0xFFDF0000UL; + outl(ui_ReadCommand, devpriv->iobase + 128 + 12); + + /* Read the channel number */ + ui_ChannelNumber = inl(devpriv->iobase + 128 + 60); + + /* Read the digital temperature value */ + ui_DigitalTemperature = inl(devpriv->iobase + 128 + 60); + + /* send signal to the sample */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + } else if ((ui_StatusRegister2 & 0x1) == 0x1) { + /* send signal to the sample */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + } + + return; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c new file mode 100644 index 00000000000..a633957890d --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c @@ -0,0 +1,2413 @@ +/* + * 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. + * + */ + +/* Card Specific information */ +#define APCI1500_ADDRESS_RANGE 4 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI1500_DIGITAL_OP 2 +#define APCI1500_DIGITAL_IP 0 +#define APCI1500_AND 2 +#define APCI1500_OR 4 +#define APCI1500_OR_PRIORITY 6 +#define APCI1500_CLK_SELECT 0 +#define COUNTER1 0 +#define COUNTER2 1 +#define COUNTER3 2 +#define APCI1500_COUNTER 0x20 +#define APCI1500_TIMER 0 +#define APCI1500_WATCHDOG 0 +#define APCI1500_SINGLE 0 +#define APCI1500_CONTINUOUS 0x80 +#define APCI1500_DISABLE 0 +#define APCI1500_ENABLE 1 +#define APCI1500_SOFTWARE_TRIGGER 0x4 +#define APCI1500_HARDWARE_TRIGGER 0x10 +#define APCI1500_SOFTWARE_GATE 0 +#define APCI1500_HARDWARE_GATE 0x8 +#define START 0 +#define STOP 1 +#define TRIGGER 2 + +/* + * Zillog I/O enumeration + */ +enum { + APCI1500_Z8536_PORT_C, + APCI1500_Z8536_PORT_B, + APCI1500_Z8536_PORT_A, + APCI1500_Z8536_CONTROL_REGISTER +}; + +/* + * Z8536 CIO Internal Address + */ +enum { + APCI1500_RW_MASTER_INTERRUPT_CONTROL, + APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + APCI1500_RW_PORT_A_INTERRUPT_CONTROL, + APCI1500_RW_PORT_B_INTERRUPT_CONTROL, + APCI1500_RW_TIMER_COUNTER_INTERRUPT_VECTOR, + APCI1500_RW_PORT_C_DATA_PCITCH_POLARITY, + APCI1500_RW_PORT_C_DATA_DIRECTION, + APCI1500_RW_PORT_C_SPECIAL_IO_CONTROL, + + APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + APCI1500_RW_CPT_TMR1_CMD_STATUS, + APCI1500_RW_CPT_TMR2_CMD_STATUS, + APCI1500_RW_CPT_TMR3_CMD_STATUS, + APCI1500_RW_PORT_A_DATA, + APCI1500_RW_PORT_B_DATA, + APCI1500_RW_PORT_C_DATA, + + APCI1500_R_CPT_TMR1_VALUE_HIGH, + APCI1500_R_CPT_TMR1_VALUE_LOW, + APCI1500_R_CPT_TMR2_VALUE_HIGH, + APCI1500_R_CPT_TMR2_VALUE_LOW, + APCI1500_R_CPT_TMR3_VALUE_HIGH, + APCI1500_R_CPT_TMR3_VALUE_LOW, + APCI1500_RW_CPT_TMR1_TIME_CST_HIGH, + APCI1500_RW_CPT_TMR1_TIME_CST_LOW, + APCI1500_RW_CPT_TMR2_TIME_CST_HIGH, + APCI1500_RW_CPT_TMR2_TIME_CST_LOW, + APCI1500_RW_CPT_TMR3_TIME_CST_HIGH, + APCI1500_RW_CPT_TMR3_TIME_CST_LOW, + APCI1500_RW_CPT_TMR1_MODE_SPECIFICATION, + APCI1500_RW_CPT_TMR2_MODE_SPECIFICATION, + APCI1500_RW_CPT_TMR3_MODE_SPECIFICATION, + APCI1500_R_CURRENT_VECTOR, + + APCI1500_RW_PORT_A_SPECIFICATION, + APCI1500_RW_PORT_A_HANDSHAKE_SPECIFICATION, + APCI1500_RW_PORT_A_DATA_PCITCH_POLARITY, + APCI1500_RW_PORT_A_DATA_DIRECTION, + APCI1500_RW_PORT_A_SPECIAL_IO_CONTROL, + APCI1500_RW_PORT_A_PATTERN_POLARITY, + APCI1500_RW_PORT_A_PATTERN_TRANSITION, + APCI1500_RW_PORT_A_PATTERN_MASK, + + APCI1500_RW_PORT_B_SPECIFICATION, + APCI1500_RW_PORT_B_HANDSHAKE_SPECIFICATION, + APCI1500_RW_PORT_B_DATA_PCITCH_POLARITY, + APCI1500_RW_PORT_B_DATA_DIRECTION, + APCI1500_RW_PORT_B_SPECIAL_IO_CONTROL, + APCI1500_RW_PORT_B_PATTERN_POLARITY, + APCI1500_RW_PORT_B_PATTERN_TRANSITION, + APCI1500_RW_PORT_B_PATTERN_MASK +}; + +static int i_TimerCounter1Init; +static int i_TimerCounter2Init; +static int i_WatchdogCounter3Init; +static int i_Event1Status, i_Event2Status; +static int i_TimerCounterWatchdogInterrupt; +static int i_Logic, i_CounterLogic; +static int i_InterruptMask; +static int i_InputChannel; +static int i_TimerCounter1Enabled, i_TimerCounter2Enabled, + i_WatchdogCounter3Enabled; + +/* + * An event can be generated for each port. The first event is related to the + * first 8 channels (port 1) and the second to the following 6 channels (port 2) + * An interrupt is generated when one or both events have occurred. + * + * data[0] Number of the input port on which the event will take place (1 or 2) + * data[1] The event logic for port 1 has three possibilities: + * APCI1500_AND This logic links the inputs with an AND logic. + * APCI1500_OR This logic links the inputs with a OR logic. + * APCI1500_OR_PRIORITY This logic links the inputs with a priority OR + * logic. Input 1 has the highest priority level + * and input 8 the smallest. + * For the second port the user has 1 possibility: + * APCI1500_OR This logic links the inputs with a polarity OR logic + * data[2] These 8-character word for port1 and 6-character word for port 2 + * give the mask of the event. Each place gives the state of the input + * channels and can have one of these six characters + * 0 This input must be on 0 + * 1 This input must be on 1 + * 2 This input reacts to a falling edge + * 3 This input reacts to a rising edge + * 4 This input reacts to both edges + * 5 This input is not used for event + */ +static int apci1500_di_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + int i_PatternPolarity = 0, i_PatternTransition = 0, i_PatternMask = 0; + int i_MaxChannel = 0, i_Count = 0, i_EventMask = 0; + int i_PatternTransitionCount = 0, i_RegValue; + int i; + + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Disables the main interrupt on the board */ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + if (data[0] == 1) { + i_MaxChannel = 8; + } /* if (data[0] == 1) */ + else { + if (data[0] == 2) { + i_MaxChannel = 6; + } /* if(data[0]==2) */ + else { + dev_warn(dev->hw_dev, + "The specified port event does not exist\n"); + return -EINVAL; + } /* else if(data[0]==2) */ + } /* else if (data[0] == 1) */ + switch (data[1]) { + case 0: + data[1] = APCI1500_AND; + break; + case 1: + data[1] = APCI1500_OR; + break; + case 2: + data[1] = APCI1500_OR_PRIORITY; + break; + default: + dev_warn(dev->hw_dev, + "The specified interrupt logic does not exist\n"); + return -EINVAL; + } /* switch(data[1]); */ + + i_Logic = data[1]; + for (i_Count = i_MaxChannel, i = 0; i_Count > 0; i_Count--, i++) { + i_EventMask = data[2 + i]; + switch (i_EventMask) { + case 0: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + break; + case 1: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + i_PatternPolarity = + i_PatternPolarity | (1 << (i_MaxChannel - + i_Count)); + break; + case 2: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + i_PatternTransition = + i_PatternTransition | (1 << (i_MaxChannel - + i_Count)); + break; + case 3: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + i_PatternPolarity = + i_PatternPolarity | (1 << (i_MaxChannel - + i_Count)); + i_PatternTransition = + i_PatternTransition | (1 << (i_MaxChannel - + i_Count)); + break; + case 4: + i_PatternTransition = + i_PatternTransition | (1 << (i_MaxChannel - + i_Count)); + break; + case 5: + break; + default: + dev_warn(dev->hw_dev, + "The option indicated in the event mask does not exist\n"); + return -EINVAL; + } /* switch(i_EventMask) */ + } /* for (i_Count = i_MaxChannel; i_Count >0;i_Count --) */ + + if (data[0] == 1) { + /* Test the interrupt logic */ + + if (data[1] == APCI1500_AND || + data[1] == APCI1500_OR || + data[1] == APCI1500_OR_PRIORITY) { + /* Tests if a transition was declared */ + /* for a OR PRIORITY logic */ + + if (data[1] == APCI1500_OR_PRIORITY + && i_PatternTransition != 0) { + dev_warn(dev->hw_dev, + "Transition error on an OR PRIORITY logic\n"); + return -EINVAL; + } /* if (data[1]== APCI1500_OR_PRIORITY && i_PatternTransition != 0) */ + + /* Tests if more than one transition */ + /* was declared for an AND logic */ + + if (data[1] == APCI1500_AND) { + for (i_Count = 0; i_Count < 8; i_Count++) { + i_PatternTransitionCount = + i_PatternTransitionCount + + ((i_PatternTransition >> + i_Count) & 0x1); + + } /* for (i_Count = 0; i_Count < 8; i_Count++) */ + + if (i_PatternTransitionCount > 1) { + dev_warn(dev->hw_dev, + "Transition error on an AND logic\n"); + return -EINVAL; + } /* if (i_PatternTransitionCount > 1) */ + } /* if (data[1]== APCI1500_AND) */ + + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Disable Port A */ + outb(0xF0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the polarity register of port 1 */ + outb(APCI1500_RW_PORT_A_PATTERN_POLARITY, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternPolarity, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the pattern mask register of */ + /* port 1 */ + outb(APCI1500_RW_PORT_A_PATTERN_MASK, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternMask, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the pattern transition register */ + /* of port 1 */ + outb(APCI1500_RW_PORT_A_PATTERN_TRANSITION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternTransition, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification mask */ + /* register of port 1 */ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification mask */ + /* register of port 1 */ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Port A new mode */ + + i_RegValue = (i_RegValue & 0xF9) | data[1] | 0x9; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + i_Event1Status = 1; + + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Enable Port A */ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* if(data[1]==APCI1500_AND||data[1]==APCI1500_OR||data[1]==APCI1500_OR_PRIORITY) */ + else { + dev_warn(dev->hw_dev, + "The choice for interrupt logic does not exist\n"); + return -EINVAL; + } /* else }// if(data[1]==APCI1500_AND||data[1]==APCI1500_OR||data[1]==APCI1500_OR_PRIORITY) */ + } /* if (data[0]== 1) */ + + /* Test if event setting for port 2 */ + + if (data[0] == 2) { + /* Test the event logic */ + + if (data[1] == APCI1500_OR) { + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Disable Port B */ + outb(0x74, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the mode specification mask */ + /* register of port B */ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification mask */ + /* register of port B */ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = i_RegValue & 0xF9; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects error channels 1 and 2 */ + + i_PatternMask = (i_PatternMask | 0xC0); + i_PatternPolarity = (i_PatternPolarity | 0xC0); + i_PatternTransition = (i_PatternTransition | 0xC0); + + /* Selects the polarity register of port 2 */ + outb(APCI1500_RW_PORT_B_PATTERN_POLARITY, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternPolarity, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the pattern transition register */ + /* of port 2 */ + outb(APCI1500_RW_PORT_B_PATTERN_TRANSITION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternTransition, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the pattern Mask register */ + /* of port 2 */ + + outb(APCI1500_RW_PORT_B_PATTERN_MASK, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternMask, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification mask */ + /* register of port 2 */ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the mode specification mask */ + /* register of port 2 */ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = (i_RegValue & 0xF9) | 4; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + i_Event2Status = 1; + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Enable Port B */ + + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if (data[1] == APCI1500_OR) */ + else { + dev_warn(dev->hw_dev, + "The choice for interrupt logic does not exist\n"); + return -EINVAL; + } /* elseif (data[1] == APCI1500_OR) */ + } /* if(data[0]==2) */ + + return insn->n; +} + +/* + * Allows or disallows a port event + * + * data[0] 0 = Start input event, 1 = Stop input event + * data[1] Number of port (1 or 2) + */ +static int apci1500_di_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + int i_Event1InterruptStatus = 0, i_Event2InterruptStatus = + 0, i_RegValue; + + switch (data[0]) { + case START: + /* Tests the port number */ + + if (data[1] == 1 || data[1] == 2) { + /* Test if port 1 selected */ + + if (data[1] == 1) { + /* Test if event initialised */ + if (i_Event1Status == 1) { + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Disable Port A */ + outb(0xF0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of */ + /* port 1 */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Allows the pattern interrupt */ + outb(0xC0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Enable Port A */ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event1InterruptStatus = 1; + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Authorizes the main interrupt on the board */ + outb(0xD0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* if(i_Event1Status==1) */ + else { + dev_warn(dev->hw_dev, + "Event 1 not initialised\n"); + return -EINVAL; + } /* else if(i_Event1Status==1) */ + } /* if (data[1]==1) */ + if (data[1] == 2) { + + if (i_Event2Status == 1) { + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Disable Port B */ + outb(0x74, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of */ + /* port 2 */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Allows the pattern interrupt */ + outb(0xC0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Enable Port B */ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Authorizes the main interrupt on the board */ + outb(0xD0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event2InterruptStatus = 1; + } /* if(i_Event2Status==1) */ + else { + dev_warn(dev->hw_dev, + "Event 2 not initialised\n"); + return -EINVAL; + } /* else if(i_Event2Status==1) */ + } /* if(data[1]==2) */ + } /* if (data[1] == 1 || data[0] == 2) */ + else { + dev_warn(dev->hw_dev, + "The port parameter is in error\n"); + return -EINVAL; + } /* else if (data[1] == 1 || data[0] == 2) */ + + break; + + case STOP: + /* Tests the port number */ + + if (data[1] == 1 || data[1] == 2) { + /* Test if port 1 selected */ + + if (data[1] == 1) { + /* Test if event initialised */ + if (i_Event1Status == 1) { + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Disable Port A */ + outb(0xF0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of */ + /* port 1 */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Inhibits the pattern interrupt */ + outb(0xE0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Enable Port A */ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event1InterruptStatus = 0; + } /* if(i_Event1Status==1) */ + else { + dev_warn(dev->hw_dev, + "Event 1 not initialised\n"); + return -EINVAL; + } /* else if(i_Event1Status==1) */ + } /* if (data[1]==1) */ + if (data[1] == 2) { + /* Test if event initialised */ + if (i_Event2Status == 1) { + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Disable Port B */ + outb(0x74, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of */ + /* port 2 */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Inhibits the pattern interrupt */ + outb(0xE0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Enable Port B */ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event2InterruptStatus = 0; + } /* if(i_Event2Status==1) */ + else { + dev_warn(dev->hw_dev, + "Event 2 not initialised\n"); + return -EINVAL; + } /* else if(i_Event2Status==1) */ + } /* if(data[1]==2) */ + + } /* if (data[1] == 1 || data[1] == 2) */ + else { + dev_warn(dev->hw_dev, + "The port parameter is in error\n"); + return -EINVAL; + } /* else if (data[1] == 1 || data[1] == 2) */ + break; + default: + dev_warn(dev->hw_dev, + "The option of START/STOP logic does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + + return insn->n; +} + +/* + * Return the status of the digital input + */ +static int apci1500_di_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + int i_DummyRead = 0; + + /* Software reset */ + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(1, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master configuration control register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0xF4, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification register of port A */ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data path polarity register of port A */ + outb(APCI1500_RW_PORT_A_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port A means 1 */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data direction register of port A */ + outb(APCI1500_RW_PORT_A_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port A: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port A */ + outb(APCI1500_RW_PORT_A_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification register of port B */ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data path polarity register of port B */ + outb(APCI1500_RW_PORT_B_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* A high level of port B means 1 */ + outb(0x7F, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port B */ + outb(APCI1500_RW_PORT_B_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port B: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port B */ + outb(APCI1500_RW_PORT_B_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data path polarity register of port C */ + outb(APCI1500_RW_PORT_C_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port C means 1 */ + outb(0x9, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port C */ + outb(APCI1500_RW_PORT_C_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs except channel 1 */ + outb(0x0E, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the special IO register of port C */ + outb(APCI1500_RW_PORT_C_SPECIAL_IO_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes it */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of timer 1 */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates Timer 2 interrupt management: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of Timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates interrupt management of timer 3: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes all interrupts */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + return insn->n; +} + +static int apci1500_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + + data[1] = inw(devpriv->i_IobaseAddon + APCI1500_DIGITAL_IP); + + return insn->n; +} + +/* + * Configures the digital output memory and the digital output error interrupt + * + * data[1] 1 = Enable the voltage error interrupt + * 2 = Disable the voltage error interrupt + */ +static int apci1500_do_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + + devpriv->b_OutputMemoryStatus = data[0]; + return insn->n; +} + +/* + * Writes port value to the selected port + */ +static int apci1500_do_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + static unsigned int ui_Temp; + unsigned int ui_Temp1; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + + if (!devpriv->b_OutputMemoryStatus) { + ui_Temp = 0; + + } /* if(!devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outw(data[0], + devpriv->i_IobaseAddon + APCI1500_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + + case 4: + data[0] = + (data[0] << (4 * + data[2])) | ui_Temp; + break; + + case 8: + data[0] = + (data[0] << (8 * + data[2])) | ui_Temp; + break; + + case 15: + data[0] = data[0] | ui_Temp; + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->i_IobaseAddon + + APCI1500_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + dev_warn(dev->hw_dev, + "Specified channel not supported\n"); + return -EINVAL; + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + (data[0] << ui_NoOfChannel) ^ + 0xffffffff; + data[0] = data[0] & ui_Temp; + outw(data[0], + devpriv->i_IobaseAddon + + APCI1500_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 4: + data[0] = ~data[0] & 0xf; + ui_Temp1 = 15; + ui_Temp1 = + ui_Temp1 << 4 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (4 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 8: + data[0] = ~data[0] & 0xff; + ui_Temp1 = 255; + ui_Temp1 = + ui_Temp1 << 8 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (8 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 15: + break; + + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->i_IobaseAddon + + APCI1500_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + dev_warn(dev->hw_dev, + "Specified channel not supported\n"); + return -EINVAL; + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + dev_warn(dev->hw_dev, + "Specified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + ui_Temp = data[0]; + return insn->n; +} + +/* + * Configures The Watchdog + * + * data[0] 0 = APCI1500_115_KHZ, 1 = APCI1500_3_6_KHZ, 2 = APCI1500_1_8_KHZ + * data[1] 0 = Counter1/Timer1, 1 = Counter2/Timer2, 2 = Counter3/Watchdog + * data[2] 0 = Counter, 1 = Timer/Watchdog + * data[3] This parameter has two meanings. If the counter/timer is used as + * a counter the limit value of the counter is given. If the counter/timer + * is used as a timer, the divider factor for the output is given. + * data[4] 0 = APCI1500_CONTINUOUS, 1 = APCI1500_SINGLE + * data[5] 0 = Software Trigger, 1 = Hardware Trigger + * data[6] 0 = Software gate, 1 = Hardware gate + * data[7] 0 = Interrupt Disable, 1 = Interrupt Enable + */ +static int apci1500_timer_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + int i_TimerCounterMode, i_MasterConfiguration; + + devpriv->tsk_Current = current; + +/* Selection of the input clock */ + if (data[0] == 0 || data[0] == 1 || data[0] == 2) { + outw(data[0], devpriv->i_IobaseAddon + APCI1500_CLK_SELECT); + } /* if(data[0]==0||data[0]==1||data[0]==2) */ + else { + if (data[0] != 3) { + dev_warn(dev->hw_dev, + "The option for input clock selection does not exist\n"); + return -EINVAL; + } /* if(data[0]!=3) */ + } /* elseif(data[0]==0||data[0]==1||data[0]==2) */ + /* Select the counter/timer */ + switch (data[1]) { + case COUNTER1: + /* selecting counter or timer */ + switch (data[2]) { + case 0: + data[2] = APCI1500_COUNTER; + break; + case 1: + data[2] = APCI1500_TIMER; + break; + default: + dev_warn(dev->hw_dev, + "This choice is not a timer nor a counter\n"); + return -EINVAL; + } /* switch(data[2]) */ + + /* Selecting single or continuous mode */ + switch (data[4]) { + case 0: + data[4] = APCI1500_CONTINUOUS; + break; + case 1: + data[4] = APCI1500_SINGLE; + break; + default: + dev_warn(dev->hw_dev, + "This option for single/continuous mode does not exist\n"); + return -EINVAL; + } /* switch(data[4]) */ + + i_TimerCounterMode = data[2] | data[4] | 7; + /* Test the reload value */ + + if ((data[3] >= 0) && (data[3] <= 65535)) { + if (data[7] == APCI1500_ENABLE + || data[7] == APCI1500_DISABLE) { + + /* Selects the mode register of timer/counter 1 */ + outb(APCI1500_RW_CPT_TMR1_MODE_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Writes the new mode */ + outb(i_TimerCounterMode, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the constant register of timer/counter 1 */ + + outb(APCI1500_RW_CPT_TMR1_TIME_CST_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the low value */ + + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the constant register of timer/counter 1 */ + + outb(APCI1500_RW_CPT_TMR1_TIME_CST_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the high value */ + + data[3] = data[3] >> 8; + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master configuration register */ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Reads the register */ + + i_MasterConfiguration = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Enables timer/counter 1 and triggers timer/counter 1 */ + + i_MasterConfiguration = + i_MasterConfiguration | 0x40; + + /* Selects the master configuration register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the new configuration */ + outb(i_MasterConfiguration, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the commands register of */ + /* timer/counter 1 */ + + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Disable timer/counter 1 */ + + outb(0x0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the commands register of */ + /* timer/counter 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Trigger timer/counter 1 */ + outb(0x2, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + else { + dev_warn(dev->hw_dev, + "Error in selection of interrupt enable or disable\n"); + return -EINVAL; + } /* elseif(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + } /* if ((data[3]>= 0) && (data[3] <= 65535)) */ + else { + dev_warn(dev->hw_dev, + "Error in selection of reload value\n"); + return -EINVAL; + } /* else if ((data[3]>= 0) && (data[3] <= 65535)) */ + i_TimerCounterWatchdogInterrupt = data[7]; + i_TimerCounter1Init = 1; + break; + + case COUNTER2: /* selecting counter or timer */ + switch (data[2]) { + case 0: + data[2] = APCI1500_COUNTER; + break; + case 1: + data[2] = APCI1500_TIMER; + break; + default: + dev_warn(dev->hw_dev, + "This choice is not a timer nor a counter\n"); + return -EINVAL; + } /* switch(data[2]) */ + + /* Selecting single or continuous mode */ + switch (data[4]) { + case 0: + data[4] = APCI1500_CONTINUOUS; + break; + case 1: + data[4] = APCI1500_SINGLE; + break; + default: + dev_warn(dev->hw_dev, + "This option for single/continuous mode does not exist\n"); + return -EINVAL; + } /* switch(data[4]) */ + + /* Selecting software or hardware trigger */ + switch (data[5]) { + case 0: + data[5] = APCI1500_SOFTWARE_TRIGGER; + break; + case 1: + data[5] = APCI1500_HARDWARE_TRIGGER; + break; + default: + dev_warn(dev->hw_dev, + "This choice for software or hardware trigger does not exist\n"); + return -EINVAL; + } /* switch(data[5]) */ + + /* Selecting software or hardware gate */ + switch (data[6]) { + case 0: + data[6] = APCI1500_SOFTWARE_GATE; + break; + case 1: + data[6] = APCI1500_HARDWARE_GATE; + break; + default: + dev_warn(dev->hw_dev, + "This choice for software or hardware gate does not exist\n"); + return -EINVAL; + } /* switch(data[6]) */ + + i_TimerCounterMode = data[2] | data[4] | data[5] | data[6] | 7; + + /* Test the reload value */ + + if ((data[3] >= 0) && (data[3] <= 65535)) { + if (data[7] == APCI1500_ENABLE + || data[7] == APCI1500_DISABLE) { + + /* Selects the mode register of timer/counter 2 */ + outb(APCI1500_RW_CPT_TMR2_MODE_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Writes the new mode */ + outb(i_TimerCounterMode, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the constant register of timer/counter 2 */ + + outb(APCI1500_RW_CPT_TMR2_TIME_CST_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the low value */ + + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the constant register of timer/counter 2 */ + + outb(APCI1500_RW_CPT_TMR2_TIME_CST_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the high value */ + + data[3] = data[3] >> 8; + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master configuration register */ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Reads the register */ + + i_MasterConfiguration = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Enables timer/counter 2 and triggers timer/counter 2 */ + + i_MasterConfiguration = + i_MasterConfiguration | 0x20; + + /* Selects the master configuration register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the new configuration */ + outb(i_MasterConfiguration, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the commands register of */ + /* timer/counter 2 */ + + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Disable timer/counter 2 */ + + outb(0x0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the commands register of */ + /* timer/counter 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Trigger timer/counter 1 */ + outb(0x2, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + else { + dev_warn(dev->hw_dev, + "Error in selection of interrupt enable or disable\n"); + return -EINVAL; + } /* elseif(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + } /* if ((data[3]>= 0) && (data[3] <= 65535)) */ + else { + dev_warn(dev->hw_dev, + "Error in selection of reload value\n"); + return -EINVAL; + } /* else if ((data[3]>= 0) && (data[3] <= 65535)) */ + i_TimerCounterWatchdogInterrupt = data[7]; + i_TimerCounter2Init = 1; + break; + + case COUNTER3: /* selecting counter or watchdog */ + switch (data[2]) { + case 0: + data[2] = APCI1500_COUNTER; + break; + case 1: + data[2] = APCI1500_WATCHDOG; + break; + default: + dev_warn(dev->hw_dev, + "This choice is not a watchdog nor a counter\n"); + return -EINVAL; + } /* switch(data[2]) */ + + /* Selecting single or continuous mode */ + switch (data[4]) { + case 0: + data[4] = APCI1500_CONTINUOUS; + break; + case 1: + data[4] = APCI1500_SINGLE; + break; + default: + dev_warn(dev->hw_dev, + "This option for single/continuous mode does not exist\n"); + return -EINVAL; + } /* switch(data[4]) */ + + /* Selecting software or hardware gate */ + switch (data[6]) { + case 0: + data[6] = APCI1500_SOFTWARE_GATE; + break; + case 1: + data[6] = APCI1500_HARDWARE_GATE; + break; + default: + dev_warn(dev->hw_dev, + "This choice for software or hardware gate does not exist\n"); + return -EINVAL; + } /* switch(data[6]) */ + + /* Test if used for watchdog */ + + if (data[2] == APCI1500_WATCHDOG) { + /* - Enables the output line */ + /* - Enables retrigger */ + /* - Pulses output */ + i_TimerCounterMode = data[2] | data[4] | 0x54; + } /* if (data[2] == APCI1500_WATCHDOG) */ + else { + i_TimerCounterMode = data[2] | data[4] | data[6] | 7; + } /* elseif (data[2] == APCI1500_WATCHDOG) */ + /* Test the reload value */ + + if ((data[3] >= 0) && (data[3] <= 65535)) { + if (data[7] == APCI1500_ENABLE + || data[7] == APCI1500_DISABLE) { + + /* Selects the mode register of watchdog/counter 3 */ + outb(APCI1500_RW_CPT_TMR3_MODE_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Writes the new mode */ + outb(i_TimerCounterMode, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the constant register of watchdog/counter 3 */ + + outb(APCI1500_RW_CPT_TMR3_TIME_CST_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the low value */ + + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the constant register of watchdog/counter 3 */ + + outb(APCI1500_RW_CPT_TMR3_TIME_CST_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the high value */ + + data[3] = data[3] >> 8; + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master configuration register */ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Reads the register */ + + i_MasterConfiguration = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Enables watchdog/counter 3 and triggers watchdog/counter 3 */ + + i_MasterConfiguration = + i_MasterConfiguration | 0x10; + + /* Selects the master configuration register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Writes the new configuration */ + outb(i_MasterConfiguration, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Test if COUNTER */ + if (data[2] == APCI1500_COUNTER) { + + /* Selects the command register of */ + /* watchdog/counter 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Disable the watchdog/counter 3 and starts it */ + outb(0x0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the command register of */ + /* watchdog/counter 3 */ + + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Trigger the watchdog/counter 3 and starts it */ + outb(0x2, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* elseif(data[2]==APCI1500_COUNTER) */ + + } /* if(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + else { + dev_warn(dev->hw_dev, + "Error in selection of interrupt enable or disable\n"); + return -EINVAL; + } /* elseif(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + } /* if ((data[3]>= 0) && (data[3] <= 65535)) */ + else { + dev_warn(dev->hw_dev, + "Error in selection of reload value\n"); + return -EINVAL; + } /* else if ((data[3]>= 0) && (data[3] <= 65535)) */ + i_TimerCounterWatchdogInterrupt = data[7]; + i_WatchdogCounter3Init = 1; + break; + + default: + dev_warn(dev->hw_dev, + "The specified counter/timer option does not exist\n"); + return -EINVAL; + } /* switch(data[1]) */ + i_CounterLogic = data[2]; + return insn->n; +} + +/* + * Start / Stop or trigger the timer counter or Watchdog + * + * data[0] 0 = Counter1/Timer1, 1 = Counter2/Timer2, 2 = Counter3/Watchdog + * data[1] 0 = Start, 1 = Stop, 2 = Trigger + * data[2] 0 = Counter, 1 = Timer/Watchdog + */ +static int apci1500_timer_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + int i_CommandAndStatusValue; + + switch (data[0]) { + case COUNTER1: + switch (data[1]) { + case START: + if (i_TimerCounter1Init == 1) { + if (i_TimerCounterWatchdogInterrupt == 1) { + i_CommandAndStatusValue = 0xC4; /* Enable the interrupt */ + } /* if(i_TimerCounterWatchdogInterrupt==1) */ + else { + i_CommandAndStatusValue = 0xE4; /* disable the interrupt */ + } /* elseif(i_TimerCounterWatchdogInterrupt==1) */ + /* Starts timer/counter 1 */ + i_TimerCounter1Enabled = 1; + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter1Init==1) */ + else { + dev_warn(dev->hw_dev, + "Counter/Timer1 not configured\n"); + return -EINVAL; + } + break; + + case STOP: + + /* Stop timer/counter 1 */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x00, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_TimerCounter1Enabled = 0; + break; + + case TRIGGER: + if (i_TimerCounter1Init == 1) { + if (i_TimerCounter1Enabled == 1) { + /* Set Trigger and gate */ + + i_CommandAndStatusValue = 0x6; + } /* if( i_TimerCounter1Enabled==1) */ + else { + /* Set Trigger */ + + i_CommandAndStatusValue = 0x2; + } /* elseif(i_TimerCounter1Enabled==1) */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter1Init==1) */ + else { + dev_warn(dev->hw_dev, + "Counter/Timer1 not configured\n"); + return -EINVAL; + } + break; + + default: + dev_warn(dev->hw_dev, + "The specified option for start/stop/trigger does not exist\n"); + return -EINVAL; + } /* switch(data[1]) */ + break; + + case COUNTER2: + switch (data[1]) { + case START: + if (i_TimerCounter2Init == 1) { + if (i_TimerCounterWatchdogInterrupt == 1) { + i_CommandAndStatusValue = 0xC4; /* Enable the interrupt */ + } /* if(i_TimerCounterWatchdogInterrupt==1) */ + else { + i_CommandAndStatusValue = 0xE4; /* disable the interrupt */ + } /* elseif(i_TimerCounterWatchdogInterrupt==1) */ + /* Starts timer/counter 2 */ + i_TimerCounter2Enabled = 1; + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter2Init==1) */ + else { + dev_warn(dev->hw_dev, + "Counter/Timer2 not configured\n"); + return -EINVAL; + } + break; + + case STOP: + + /* Stop timer/counter 2 */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x00, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_TimerCounter2Enabled = 0; + break; + case TRIGGER: + if (i_TimerCounter2Init == 1) { + if (i_TimerCounter2Enabled == 1) { + /* Set Trigger and gate */ + + i_CommandAndStatusValue = 0x6; + } /* if( i_TimerCounter2Enabled==1) */ + else { + /* Set Trigger */ + + i_CommandAndStatusValue = 0x2; + } /* elseif(i_TimerCounter2Enabled==1) */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter2Init==1) */ + else { + dev_warn(dev->hw_dev, + "Counter/Timer2 not configured\n"); + return -EINVAL; + } + break; + default: + dev_warn(dev->hw_dev, + "The specified option for start/stop/trigger does not exist\n"); + return -EINVAL; + } /* switch(data[1]) */ + break; + case COUNTER3: + switch (data[1]) { + case START: + if (i_WatchdogCounter3Init == 1) { + + if (i_TimerCounterWatchdogInterrupt == 1) { + i_CommandAndStatusValue = 0xC4; /* Enable the interrupt */ + } /* if(i_TimerCounterWatchdogInterrupt==1) */ + else { + i_CommandAndStatusValue = 0xE4; /* disable the interrupt */ + } /* elseif(i_TimerCounterWatchdogInterrupt==1) */ + /* Starts Watchdog/counter 3 */ + i_WatchdogCounter3Enabled = 1; + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* if( i_WatchdogCounter3init==1) */ + else { + dev_warn(dev->hw_dev, + "Watchdog/Counter3 not configured\n"); + return -EINVAL; + } + break; + + case STOP: + + /* Stop Watchdog/counter 3 */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x00, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_WatchdogCounter3Enabled = 0; + break; + + case TRIGGER: + switch (data[2]) { + case 0: /* triggering counter 3 */ + if (i_WatchdogCounter3Init == 1) { + if (i_WatchdogCounter3Enabled == 1) { + /* Set Trigger and gate */ + + i_CommandAndStatusValue = 0x6; + } /* if( i_WatchdogCounter3Enabled==1) */ + else { + /* Set Trigger */ + + i_CommandAndStatusValue = 0x2; + } /* elseif(i_WatchdogCounter3Enabled==1) */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_WatchdogCounter3Init==1) */ + else { + dev_warn(dev->hw_dev, + "Counter3 not configured\n"); + return -EINVAL; + } + break; + case 1: + /* triggering Watchdog 3 */ + if (i_WatchdogCounter3Init == 1) { + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x6, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_WatchdogCounter3Init==1) */ + else { + dev_warn(dev->hw_dev, + "Watchdog 3 not configured\n"); + return -EINVAL; + } + break; + default: + dev_warn(dev->hw_dev, + "Wrong choice of watchdog/counter3\n"); + return -EINVAL; + } /* switch(data[2]) */ + break; + default: + dev_warn(dev->hw_dev, + "The specified option for start/stop/trigger does not exist\n"); + return -EINVAL; + } /* switch(data[1]) */ + break; + default: + dev_warn(dev->hw_dev, + "The specified choice for counter/watchdog/timer does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + return insn->n; +} + +/* + * Read The Watchdog + * + * data[0] 0 = Counter1/Timer1, 1 = Counter2/Timer2, 2 = Counter3/Watchdog + */ +static int apci1500_timer_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + int i_CommandAndStatusValue; + + switch (data[0]) { + case COUNTER1: + /* Read counter/timer1 */ + if (i_TimerCounter1Init == 1) { + if (i_TimerCounter1Enabled == 1) { + /* Set RCC and gate */ + + i_CommandAndStatusValue = 0xC; + } /* if( i_TimerCounter1Init==1) */ + else { + /* Set RCC */ + + i_CommandAndStatusValue = 0x8; + } /* elseif(i_TimerCounter1Init==1) */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the counter register (high) */ + outb(APCI1500_R_CPT_TMR1_VALUE_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = data[0] << 8; + data[0] = data[0] & 0xff00; + outb(APCI1500_R_CPT_TMR1_VALUE_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + data[0] | inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter1Init==1) */ + else { + dev_warn(dev->hw_dev, + "Timer/Counter1 not configured\n"); + return -EINVAL; + } /* elseif( i_TimerCounter1Init==1) */ + break; + case COUNTER2: + /* Read counter/timer2 */ + if (i_TimerCounter2Init == 1) { + if (i_TimerCounter2Enabled == 1) { + /* Set RCC and gate */ + + i_CommandAndStatusValue = 0xC; + } /* if( i_TimerCounter2Init==1) */ + else { + /* Set RCC */ + + i_CommandAndStatusValue = 0x8; + } /* elseif(i_TimerCounter2Init==1) */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the counter register (high) */ + outb(APCI1500_R_CPT_TMR2_VALUE_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = data[0] << 8; + data[0] = data[0] & 0xff00; + outb(APCI1500_R_CPT_TMR2_VALUE_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + data[0] | inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter2Init==1) */ + else { + dev_warn(dev->hw_dev, + "Timer/Counter2 not configured\n"); + return -EINVAL; + } /* elseif( i_TimerCounter2Init==1) */ + break; + case COUNTER3: + /* Read counter/watchdog2 */ + if (i_WatchdogCounter3Init == 1) { + if (i_WatchdogCounter3Enabled == 1) { + /* Set RCC and gate */ + + i_CommandAndStatusValue = 0xC; + } /* if( i_TimerCounter2Init==1) */ + else { + /* Set RCC */ + + i_CommandAndStatusValue = 0x8; + } /* elseif(i_WatchdogCounter3Init==1) */ + + /* Selects the commands and status register */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the counter register (high) */ + outb(APCI1500_R_CPT_TMR3_VALUE_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = data[0] << 8; + data[0] = data[0] & 0xff00; + outb(APCI1500_R_CPT_TMR3_VALUE_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + data[0] | inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_WatchdogCounter3Init==1) */ + else { + dev_warn(dev->hw_dev, + "WatchdogCounter3 not configured\n"); + return -EINVAL; + } /* elseif( i_WatchdogCounter3Init==1) */ + break; + default: + dev_warn(dev->hw_dev, + "The choice of timer/counter/watchdog does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + + return insn->n; +} + +/* + * Read the interrupt mask + * + * data[0] The interrupt mask value + * data[1] Channel Number + */ +static int apci1500_timer_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[0] = i_InterruptMask; + data[1] = i_InputChannel; + i_InterruptMask = 0; + return insn->n; +} + +/* + * Configures the interrupt registers + */ +static int apci1500_do_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Status; + int i_RegValue; + int i_Constant; + + devpriv->tsk_Current = current; + outl(0x0, devpriv->i_IobaseAmcc + 0x38); + if (data[0] == 1) { + i_Constant = 0xC0; + } /* if(data[0]==1) */ + else { + if (data[0] == 0) { + i_Constant = 0x00; + } /* if{data[0]==0) */ + else { + dev_warn(dev->hw_dev, + "The parameter passed to driver is in error for enabling the voltage interrupt\n"); + return -EINVAL; + } /* else if(data[0]==0) */ + } /* elseif(data[0]==1) */ + + /* Selects the mode specification register of port B */ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Writes the new configuration (APCI1500_OR) */ + i_RegValue = (i_RegValue & 0xF9) | APCI1500_OR; + + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Authorises the interrupt on the board */ + outb(0xC0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the pattern polarity register of port B */ + outb(APCI1500_RW_PORT_B_PATTERN_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_Constant, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the pattern transition register of port B */ + outb(APCI1500_RW_PORT_B_PATTERN_TRANSITION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_Constant, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the pattern mask register of port B */ + outb(APCI1500_RW_PORT_B_PATTERN_MASK, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_Constant, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of port A */ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of port B */ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of timer 1 */ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of timer 2 */ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the command and status register of timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of timer 3 */ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Authorizes the main interrupt on the board */ + outb(0xD0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Enables the PCI interrupt */ + outl(0x3000, devpriv->i_IobaseAmcc + 0x38); + ui_Status = inl(devpriv->i_IobaseAmcc + 0x10); + ui_Status = inl(devpriv->i_IobaseAmcc + 0x38); + outl(0x23000, devpriv->i_IobaseAmcc + 0x38); + + return insn->n; +} + +static void apci1500_interrupt(int irq, void *d) +{ + + struct comedi_device *dev = d; + struct addi_private *devpriv = dev->private; + unsigned int ui_InterruptStatus = 0; + int i_RegValue = 0; + i_InterruptMask = 0; + + /* Read the board interrupt status */ + ui_InterruptStatus = inl(devpriv->i_IobaseAmcc + 0x38); + + /* Test if board generated a interrupt */ + if ((ui_InterruptStatus & 0x800000) == 0x800000) { + /* Disable all Interrupt */ + /* Selects the master interrupt control register */ + /* outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL,devpriv->iobase+APCI1500_Z8536_CONTROL_REGISTER); */ + /* Disables the main interrupt on the board */ + /* outb(0x00,devpriv->iobase+APCI1500_Z8536_CONTROL_REGISTER); */ + + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of port A */ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_InterruptMask = i_InterruptMask | 1; + if (i_Logic == APCI1500_OR_PRIORITY) { + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the interrupt vector register of port A */ + outb(APCI1500_RW_PORT_A_INTERRUPT_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + i_InputChannel = 1 + (i_RegValue >> 1); + + } /* if(i_Logic==APCI1500_OR_PRIORITY) */ + else { + i_InputChannel = 0; + } /* elseif(i_Logic==APCI1500_OR_PRIORITY) */ + } /* if ((i_RegValue & 0x60) == 0x60) */ + + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of port B */ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Reads port B */ + i_RegValue = + inb((unsigned int) devpriv->iobase + + APCI1500_Z8536_PORT_B); + + i_RegValue = i_RegValue & 0xC0; + /* Tests if this is an external error */ + + if (i_RegValue) { + /* Disable the interrupt */ + /* Selects the command and status register of port B */ + outl(0x0, devpriv->i_IobaseAmcc + 0x38); + + if (i_RegValue & 0x80) { + i_InterruptMask = + i_InterruptMask | 0x40; + } /* if (i_RegValue & 0x80) */ + + if (i_RegValue & 0x40) { + i_InterruptMask = + i_InterruptMask | 0x80; + } /* if (i_RegValue & 0x40) */ + } /* if (i_RegValue) */ + else { + i_InterruptMask = i_InterruptMask | 2; + } /* if (i_RegValue) */ + } /* if ((i_RegValue & 0x60) == 0x60) */ + + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of timer 1 */ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_InterruptMask = i_InterruptMask | 4; + } /* if ((i_RegValue & 0x60) == 0x60) */ + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of timer 2 */ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_InterruptMask = i_InterruptMask | 8; + } /* if ((i_RegValue & 0x60) == 0x60) */ + + /* Selects the command and status register of timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /* Selects the command and status register of timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the interrupt of timer 3 */ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + if (i_CounterLogic == APCI1500_COUNTER) { + i_InterruptMask = i_InterruptMask | 0x10; + } /* if(i_CounterLogic==APCI1500_COUNTER) */ + else { + i_InterruptMask = i_InterruptMask | 0x20; + } + } /* if ((i_RegValue & 0x60) == 0x60) */ + + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + /* Enable all Interrupts */ + + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Authorizes the main interrupt on the board */ + outb(0xD0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + } /* if ((ui_InterruptStatus & 0x800000) == 0x800000) */ + else { + dev_warn(dev->hw_dev, + "Interrupt from unknown source\n"); + + } /* else if ((ui_InterruptStatus & 0x800000) == 0x800000) */ + return; +} + +static int apci1500_reset(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + int i_DummyRead = 0; + + i_TimerCounter1Init = 0; + i_TimerCounter2Init = 0; + i_WatchdogCounter3Init = 0; + i_Event1Status = 0; + i_Event2Status = 0; + i_TimerCounterWatchdogInterrupt = 0; + i_Logic = 0; + i_CounterLogic = 0; + i_InterruptMask = 0; + i_InputChannel = 0; + i_TimerCounter1Enabled = 0; + i_TimerCounter2Enabled = 0; + i_WatchdogCounter3Enabled = 0; + + /* Software reset */ + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(1, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master configuration control register */ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0xF4, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification register of port A */ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data path polarity register of port A */ + outb(APCI1500_RW_PORT_A_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port A means 1 */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data direction register of port A */ + outb(APCI1500_RW_PORT_A_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port A: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port A */ + outb(APCI1500_RW_PORT_A_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the mode specification register of port B */ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data path polarity register of port B */ + outb(APCI1500_RW_PORT_B_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* A high level of port B means 1 */ + outb(0x7F, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port B */ + outb(APCI1500_RW_PORT_B_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port B: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port B */ + outb(APCI1500_RW_PORT_B_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data path polarity register of port C */ + outb(APCI1500_RW_PORT_C_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port C means 1 */ + outb(0x9, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port C */ + outb(APCI1500_RW_PORT_C_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs except channel 1 */ + outb(0x0E, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the special IO register of port C */ + outb(APCI1500_RW_PORT_C_SPECIAL_IO_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes it */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of timer 1 */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates Timer 2 interrupt management: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of Timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates interrupt management of timer 3: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes all interrupts */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* reset all the digital outputs */ + outw(0x0, devpriv->i_IobaseAddon + APCI1500_DIGITAL_OP); +/* Disable the board interrupt */ + /* Selects the master interrupt control register */ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/* Deactivates all interrupts */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/* Deactivates all interrupts */ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/* Deactivates all interrupts */ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/* Deactivates all interrupts */ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/* Deactivates all interrupts */ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/* Selects the command and status register of timer 3*/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/* Deactivates all interrupts */ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + return 0; +} 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 00000000000..0ba5385226a --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c @@ -0,0 +1,551 @@ +/* + * 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 "../addi_watchdog.h" + +#define APCI1564_ADDRESS_RANGE 128 + +/* Digital Input IRQ Function Selection */ +#define ADDIDATA_OR 0 +#define ADDIDATA_AND 1 + +/* Digital Input Interrupt Enable Disable. */ +#define APCI1564_DIGITAL_IP_INTERRUPT_ENABLE 0x4 +#define APCI1564_DIGITAL_IP_INTERRUPT_DISABLE 0xfffffffb + +/* Digital Output Interrupt Enable Disable. */ +#define APCI1564_DIGITAL_OP_VCC_INTERRUPT_ENABLE 0x1 +#define APCI1564_DIGITAL_OP_VCC_INTERRUPT_DISABLE 0xfffffffe +#define APCI1564_DIGITAL_OP_CC_INTERRUPT_ENABLE 0x2 +#define APCI1564_DIGITAL_OP_CC_INTERRUPT_DISABLE 0xfffffffd + +/* TIMER COUNTER WATCHDOG DEFINES */ +#define ADDIDATA_TIMER 0 +#define ADDIDATA_COUNTER 1 +#define ADDIDATA_WATCHDOG 2 +#define APCI1564_COUNTER1 0 +#define APCI1564_COUNTER2 1 +#define APCI1564_COUNTER3 2 +#define APCI1564_COUNTER4 3 + +/* + * devpriv->i_IobaseAmcc Register Map + */ +#define APCI1564_DI_REG 0x04 +#define APCI1564_DI_INT_MODE1_REG 0x08 +#define APCI1564_DI_INT_MODE2_REG 0x0c +#define APCI1564_DI_INT_STATUS_REG 0x10 +#define APCI1564_DI_IRQ_REG 0x14 +#define APCI1564_DO_REG 0x18 +#define APCI1564_DO_INT_CTRL_REG 0x1c +#define APCI1564_DO_INT_STATUS_REG 0x20 +#define APCI1564_DO_IRQ_REG 0x24 +#define APCI1564_WDOG_REG 0x28 +#define APCI1564_WDOG_RELOAD_REG 0x2c +#define APCI1564_WDOG_TIMEBASE_REG 0x30 +#define APCI1564_WDOG_CTRL_REG 0x34 +#define APCI1564_WDOG_STATUS_REG 0x38 +#define APCI1564_WDOG_IRQ_REG 0x3c +#define APCI1564_WDOG_WARN_TIMEVAL_REG 0x40 +#define APCI1564_WDOG_WARN_TIMEBASE_REG 0x44 +#define APCI1564_TIMER_REG 0x48 +#define APCI1564_TIMER_RELOAD_REG 0x4c +#define APCI1564_TIMER_TIMEBASE_REG 0x50 +#define APCI1564_TIMER_CTRL_REG 0x54 +#define APCI1564_TIMER_STATUS_REG 0x58 +#define APCI1564_TIMER_IRQ_REG 0x5c +#define APCI1564_TIMER_WARN_TIMEVAL_REG 0x60 +#define APCI1564_TIMER_WARN_TIMEBASE_REG 0x64 + +/* + * dev>iobase Register Map + */ +#define APCI1564_TCW_REG(x) (0x00 + ((x) * 0x20)) +#define APCI1564_TCW_RELOAD_REG(x) (0x04 + ((x) * 0x20)) +#define APCI1564_TCW_TIMEBASE_REG(x) (0x08 + ((x) * 0x20)) +#define APCI1564_TCW_CTRL_REG(x) (0x0c + ((x) * 0x20)) +#define APCI1564_TCW_STATUS_REG(x) (0x10 + ((x) * 0x20)) +#define APCI1564_TCW_IRQ_REG(x) (0x14 + ((x) * 0x20)) +#define APCI1564_TCW_WARN_TIMEVAL_REG(x) (0x18 + ((x) * 0x20)) +#define APCI1564_TCW_WARN_TIMEBASE_REG(x) (0x1c + ((x) * 0x20)) + +/* Global variables */ +static unsigned int ui_InterruptStatus_1564; +static unsigned int ui_InterruptData, ui_Type; + +/* + * Configures the digital input Subdevice + * + * data[0] 1 = Enable interrupt, 0 = Disable interrupt + * data[1] 0 = ADDIDATA Interrupt OR LOGIC, 1 = ADDIDATA Interrupt AND LOGIC + * data[2] Interrupt mask for the mode 1 + * data[3] Interrupt mask for the mode 2 + */ +static int apci1564_di_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + + devpriv->tsk_Current = current; + + /* Set the digital input logic */ + if (data[0] == ADDIDATA_ENABLE) { + data[2] = data[2] << 4; + data[3] = data[3] << 4; + outl(data[2], devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE1_REG); + outl(data[3], devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE2_REG); + if (data[1] == ADDIDATA_OR) + outl(0x4, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + else + outl(0x6, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + } else { + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE1_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE2_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + } + + return insn->n; +} + +/* + * Configures The Digital Output Subdevice. + * + * data[1] 0 = Disable VCC Interrupt, 1 = Enable VCC Interrupt + * data[2] 0 = Disable CC Interrupt, 1 = Enable CC Interrupt + */ +static int apci1564_do_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ul_Command = 0; + + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } + + if (data[0]) + devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE; + else + devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE; + + if (data[1] == ADDIDATA_ENABLE) + ul_Command = ul_Command | 0x1; + else + ul_Command = ul_Command & 0xFFFFFFFE; + + if (data[2] == ADDIDATA_ENABLE) + ul_Command = ul_Command | 0x2; + else + ul_Command = ul_Command & 0xFFFFFFFD; + + outl(ul_Command, devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG); + ui_InterruptData = inl(devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG); + devpriv->tsk_Current = current; + return insn->n; +} + +/* + * Configures The Timer, Counter or Watchdog + * + * data[0] Configure as: 0 = Timer, 1 = Counter, 2 = Watchdog + * data[1] 1 = Enable Interrupt, 0 = Disable Interrupt + * data[2] Time Unit + * data[3] Reload Value + * data[4] Timer Mode + * data[5] Timer Counter Watchdog Number + * data[6] Counter Direction + */ +static int apci1564_timer_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_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, devpriv->i_IobaseAmcc + APCI1564_WDOG_CTRL_REG); + /* Loading the Reload value */ + outl(data[3], devpriv->i_IobaseAmcc + APCI1564_WDOG_RELOAD_REG); + } else if (data[0] == ADDIDATA_TIMER) { + /* First Stop The Timer */ + ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + /* Stop The Timer */ + outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + + devpriv->b_TimerSelectMode = ADDIDATA_TIMER; + if (data[1] == 1) { + /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */ + outl(0x02, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_IRQ_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_WDOG_IRQ_REG); + outl(0x0, + dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER1)); + outl(0x0, + dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER2)); + outl(0x0, + dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER3)); + outl(0x0, + dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER4)); + } else { + /* disable Timer interrupt */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + } + + /* Loading Timebase */ + outl(data[2], devpriv->i_IobaseAmcc + APCI1564_TIMER_TIMEBASE_REG); + + /* Loading the Reload value */ + outl(data[3], devpriv->i_IobaseAmcc + APCI1564_TIMER_RELOAD_REG); + + ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + ul_Command1 = (ul_Command1 & 0xFFF719E2UL) | 2UL << 13UL | 0x10UL; + /* mode 2 */ + outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + } else if (data[0] == ADDIDATA_COUNTER) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + devpriv->b_ModeSelectRegister = data[5]; + + /* First Stop The Counter */ + ul_Command1 = inl(dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1)); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + /* Stop The Timer */ + outl(ul_Command1, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1)); + + /* Set the reload value */ + outl(data[3], dev->iobase + APCI1564_TCW_RELOAD_REG(data[5] - 1)); + + /* Set the mode : */ + /* - Disable the hardware */ + /* - Disable the counter mode */ + /* - Disable the warning */ + /* - Disable the reset */ + /* - Disable the timer mode */ + /* - Enable the counter mode */ + + ul_Command1 = + (ul_Command1 & 0xFFFC19E2UL) | 0x80000UL | + (unsigned int) ((unsigned int) data[4] << 16UL); + outl(ul_Command1, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1)); + + /* Enable or Disable Interrupt */ + ul_Command1 = (ul_Command1 & 0xFFFFF9FD) | (data[1] << 1); + outl(ul_Command1, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1)); + + /* Set the Up/Down selection */ + ul_Command1 = (ul_Command1 & 0xFFFBF9FFUL) | (data[6] << 18); + outl(ul_Command1, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1)); + } else { + dev_err(dev->class_dev, "Invalid subdevice.\n"); + } + + return insn->n; +} + +/* + * Start / Stop The Selected Timer, Counter or Watchdog + * + * data[0] Configure as: 0 = Timer, 1 = Counter, 2 = Watchdog + * data[1] 0 = Stop, 1 = Start, 2 = Trigger Clear (Only Counter) + */ +static int apci1564_timer_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ul_Command1 = 0; + + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + switch (data[1]) { + case 0: /* stop the watchdog */ + /* disable the watchdog */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_WDOG_CTRL_REG); + break; + case 1: /* start the watchdog */ + outl(0x0001, devpriv->i_IobaseAmcc + APCI1564_WDOG_CTRL_REG); + break; + case 2: /* Software trigger */ + outl(0x0201, devpriv->i_IobaseAmcc + APCI1564_WDOG_CTRL_REG); + break; + default: + dev_err(dev->class_dev, "Specified functionality does not exist.\n"); + return -EINVAL; + } + } + if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) { + if (data[1] == 1) { + ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL; + + /* Enable the Timer */ + outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + } else if (data[1] == 0) { + /* Stop The Timer */ + + ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + } + } + if (devpriv->b_TimerSelectMode == ADDIDATA_COUNTER) { + ul_Command1 = + inl(dev->iobase + + APCI1564_TCW_CTRL_REG(devpriv->b_ModeSelectRegister - 1)); + if (data[1] == 1) { + /* Start the Counter subdevice */ + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL; + } else if (data[1] == 0) { + /* Stops the Counter subdevice */ + ul_Command1 = 0; + + } else if (data[1] == 2) { + /* Clears the Counter subdevice */ + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x400; + } + outl(ul_Command1, dev->iobase + + APCI1564_TCW_CTRL_REG(devpriv->b_ModeSelectRegister - 1)); + } + return insn->n; +} + +/* + * Read The Selected Timer, Counter or Watchdog + */ +static int apci1564_timer_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ul_Command1 = 0; + + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + /* Stores the status of the Watchdog */ + data[0] = inl(devpriv->i_IobaseAmcc + APCI1564_WDOG_STATUS_REG) & 0x1; + data[1] = inl(devpriv->i_IobaseAmcc + APCI1564_WDOG_REG); + } else if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) { + /* Stores the status of the Timer */ + data[0] = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_STATUS_REG) & 0x1; + + /* Stores the Actual value of the Timer */ + data[1] = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_REG); + } else if (devpriv->b_TimerSelectMode == ADDIDATA_COUNTER) { + /* Read the Counter Actual Value. */ + data[0] = + inl(dev->iobase + + APCI1564_TCW_REG(devpriv->b_ModeSelectRegister - 1)); + ul_Command1 = + inl(dev->iobase + + APCI1564_TCW_STATUS_REG(devpriv->b_ModeSelectRegister - 1)); + + /* Get the software trigger status */ + data[1] = (unsigned char) ((ul_Command1 >> 1) & 1); + + /* Get the hardware trigger status */ + data[2] = (unsigned char) ((ul_Command1 >> 2) & 1); + + /* Get the software clear status */ + data[3] = (unsigned char) ((ul_Command1 >> 3) & 1); + + /* Get the overflow status */ + data[4] = (unsigned char) ((ul_Command1 >> 0) & 1); + } else if ((devpriv->b_TimerSelectMode != ADDIDATA_TIMER) + && (devpriv->b_TimerSelectMode != ADDIDATA_WATCHDOG) + && (devpriv->b_TimerSelectMode != ADDIDATA_COUNTER)) { + dev_err(dev->class_dev, "Invalid Subdevice!\n"); + } + return insn->n; +} + +/* + * Reads the interrupt status register + */ +static int apci1564_do_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + *data = ui_Type; + return insn->n; +} + +/* + * Interrupt handler for the interruptible digital inputs + */ +static void apci1564_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct addi_private *devpriv = dev->private; + unsigned int ui_DO, ui_DI; + unsigned int ui_Timer; + unsigned int ui_C1, ui_C2, ui_C3, ui_C4; + unsigned int ul_Command2 = 0; + + ui_DI = inl(devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG) & 0x01; + ui_DO = inl(devpriv->i_IobaseAmcc + APCI1564_DO_IRQ_REG) & 0x01; + ui_Timer = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_IRQ_REG) & 0x01; + ui_C1 = + inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER1)) & 0x1; + ui_C2 = + inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER2)) & 0x1; + ui_C3 = + inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER3)) & 0x1; + ui_C4 = + inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER4)) & 0x1; + if (ui_DI == 0 && ui_DO == 0 && ui_Timer == 0 && ui_C1 == 0 + && ui_C2 == 0 && ui_C3 == 0 && ui_C4 == 0) { + dev_err(dev->class_dev, "Interrupt from unknown source.\n"); + } + + if (ui_DI == 1) { + ui_DI = inl(devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + ui_InterruptStatus_1564 = + inl(devpriv->i_IobaseAmcc + APCI1564_DI_INT_STATUS_REG); + ui_InterruptStatus_1564 = ui_InterruptStatus_1564 & 0X000FFFF0; + /* send signal to the sample */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + /* enable the interrupt */ + outl(ui_DI, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + return; + } + + if (ui_DO == 1) { + /* Check for Digital Output interrupt Type */ + /* 1: VCC interrupt */ + /* 2: CC interrupt */ + ui_Type = inl(devpriv->i_IobaseAmcc + APCI1564_DO_INT_STATUS_REG) & 0x3; + /* Disable the Interrupt */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG); + + /* Sends signal to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + } + + if (ui_Timer == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_TIMER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Timer Interrupt */ + ul_Command2 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Timer Interrupt */ + + outl(ul_Command2, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + } + } + + if (ui_C1 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1)); + outl(0x0, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1)); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1)); + } + } + + if (ui_C2 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2)); + outl(0x0, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2)); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2)); + } + } + + if (ui_C3 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3)); + outl(0x0, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3)); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3)); + } + } + + if (ui_C4 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4)); + outl(0x0, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4)); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4)); + } + } + return; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c new file mode 100644 index 00000000000..764c8f17f8f --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c @@ -0,0 +1,2190 @@ +/** +@verbatim + +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. + +@endverbatim +*/ +/* + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstrasse 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : APCI-3120 | Compiler : GCC | + | Module name : hwdrv_apci3120.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description :APCI3120 Module. Hardware abstraction Layer for APCI3120| + +-----------------------------------------------------------------------+ + | UPDATE'S | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +#include <linux/delay.h> + +/* + * ADDON RELATED ADDITIONS + */ +/* Constant */ +#define APCI3120_ENABLE_TRANSFER_ADD_ON_LOW 0x00 +#define APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH 0x1200 +#define APCI3120_A2P_FIFO_MANAGEMENT 0x04000400L +#define APCI3120_AMWEN_ENABLE 0x02 +#define APCI3120_A2P_FIFO_WRITE_ENABLE 0x01 +#define APCI3120_FIFO_ADVANCE_ON_BYTE_2 0x20000000L +#define APCI3120_ENABLE_WRITE_TC_INT 0x00004000L +#define APCI3120_CLEAR_WRITE_TC_INT 0x00040000L +#define APCI3120_DISABLE_AMWEN_AND_A2P_FIFO_WRITE 0x0 +#define APCI3120_DISABLE_BUS_MASTER_ADD_ON 0x0 +#define APCI3120_DISABLE_BUS_MASTER_PCI 0x0 + +/* ADD_ON ::: this needed since apci supports 16 bit interface to add on */ +#define APCI3120_ADD_ON_AGCSTS_LOW 0x3C +#define APCI3120_ADD_ON_AGCSTS_HIGH (APCI3120_ADD_ON_AGCSTS_LOW + 2) +#define APCI3120_ADD_ON_MWAR_LOW 0x24 +#define APCI3120_ADD_ON_MWAR_HIGH (APCI3120_ADD_ON_MWAR_LOW + 2) +#define APCI3120_ADD_ON_MWTC_LOW 0x058 +#define APCI3120_ADD_ON_MWTC_HIGH (APCI3120_ADD_ON_MWTC_LOW + 2) + +/* AMCC */ +#define APCI3120_AMCC_OP_MCSR 0x3C +#define APCI3120_AMCC_OP_REG_INTCSR 0x38 + +/* for transfer count enable bit */ +#define AGCSTS_TC_ENABLE 0x10000000 + +/* used for test on mixture of BIP/UNI ranges */ +#define APCI3120_BIPOLAR_RANGES 4 + +#define APCI3120_ADDRESS_RANGE 16 + +#define APCI3120_DISABLE 0 +#define APCI3120_ENABLE 1 + +#define APCI3120_START 1 +#define APCI3120_STOP 0 + +#define APCI3120_EOC_MODE 1 +#define APCI3120_EOS_MODE 2 +#define APCI3120_DMA_MODE 3 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI3120_DIGITAL_OUTPUT 0x0d +#define APCI3120_RD_STATUS 0x02 +#define APCI3120_RD_FIFO 0x00 + +/* digital output insn_write ON /OFF selection */ +#define APCI3120_SET4DIGITALOUTPUTON 1 +#define APCI3120_SET4DIGITALOUTPUTOFF 0 + +/* analog output SELECT BIT */ +#define APCI3120_ANALOG_OP_CHANNEL_1 0x0000 +#define APCI3120_ANALOG_OP_CHANNEL_2 0x4000 +#define APCI3120_ANALOG_OP_CHANNEL_3 0x8000 +#define APCI3120_ANALOG_OP_CHANNEL_4 0xc000 +#define APCI3120_ANALOG_OP_CHANNEL_5 0x0000 +#define APCI3120_ANALOG_OP_CHANNEL_6 0x4000 +#define APCI3120_ANALOG_OP_CHANNEL_7 0x8000 +#define APCI3120_ANALOG_OP_CHANNEL_8 0xc000 + +/* Enable external trigger bit in nWrAddress */ +#define APCI3120_ENABLE_EXT_TRIGGER 0x8000 + +/* ANALOG OUTPUT AND INPUT DEFINE */ +#define APCI3120_UNIPOLAR 0x80 +#define APCI3120_BIPOLAR 0x00 +#define APCI3120_ANALOG_OUTPUT_1 0x08 +#define APCI3120_ANALOG_OUTPUT_2 0x0a +#define APCI3120_1_GAIN 0x00 +#define APCI3120_2_GAIN 0x10 +#define APCI3120_5_GAIN 0x20 +#define APCI3120_10_GAIN 0x30 +#define APCI3120_SEQ_RAM_ADDRESS 0x06 +#define APCI3120_RESET_FIFO 0x0c +#define APCI3120_TIMER_0_MODE_2 0x01 +#define APCI3120_TIMER_0_MODE_4 0x2 +#define APCI3120_SELECT_TIMER_0_WORD 0x00 +#define APCI3120_ENABLE_TIMER0 0x1000 +#define APCI3120_CLEAR_PR 0xf0ff +#define APCI3120_CLEAR_PA 0xfff0 +#define APCI3120_CLEAR_PA_PR (APCI3120_CLEAR_PR & APCI3120_CLEAR_PA) + +/* nWrMode_Select */ +#define APCI3120_ENABLE_SCAN 0x8 +#define APCI3120_DISABLE_SCAN (~APCI3120_ENABLE_SCAN) +#define APCI3120_ENABLE_EOS_INT 0x2 + +#define APCI3120_DISABLE_EOS_INT (~APCI3120_ENABLE_EOS_INT) +#define APCI3120_ENABLE_EOC_INT 0x1 +#define APCI3120_DISABLE_EOC_INT (~APCI3120_ENABLE_EOC_INT) +#define APCI3120_DISABLE_ALL_INTERRUPT_WITHOUT_TIMER \ + (APCI3120_DISABLE_EOS_INT & APCI3120_DISABLE_EOC_INT) +#define APCI3120_DISABLE_ALL_INTERRUPT \ + (APCI3120_DISABLE_TIMER_INT & APCI3120_DISABLE_EOS_INT & APCI3120_DISABLE_EOC_INT) + +/* status register bits */ +#define APCI3120_EOC 0x8000 +#define APCI3120_EOS 0x2000 + +/* software trigger dummy register */ +#define APCI3120_START_CONVERSION 0x02 + +/* TIMER DEFINE */ +#define APCI3120_QUARTZ_A 70 +#define APCI3120_QUARTZ_B 50 +#define APCI3120_TIMER 1 +#define APCI3120_WATCHDOG 2 +#define APCI3120_TIMER_DISABLE 0 +#define APCI3120_TIMER_ENABLE 1 +#define APCI3120_ENABLE_TIMER2 0x4000 +#define APCI3120_DISABLE_TIMER2 (~APCI3120_ENABLE_TIMER2) +#define APCI3120_ENABLE_TIMER_INT 0x04 +#define APCI3120_DISABLE_TIMER_INT (~APCI3120_ENABLE_TIMER_INT) +#define APCI3120_WRITE_MODE_SELECT 0x0e +#define APCI3120_SELECT_TIMER_0_WORD 0x00 +#define APCI3120_SELECT_TIMER_1_WORD 0x01 +#define APCI3120_TIMER_1_MODE_2 0x4 + +/* $$ BIT FOR MODE IN nCsTimerCtr1 */ +#define APCI3120_TIMER_2_MODE_0 0x0 +#define APCI3120_TIMER_2_MODE_2 0x10 +#define APCI3120_TIMER_2_MODE_5 0x30 + +/* $$ BIT FOR MODE IN nCsTimerCtr0 */ +#define APCI3120_SELECT_TIMER_2_LOW_WORD 0x02 +#define APCI3120_SELECT_TIMER_2_HIGH_WORD 0x03 + +#define APCI3120_TIMER_CRT0 0x0d +#define APCI3120_TIMER_CRT1 0x0c + +#define APCI3120_TIMER_VALUE 0x04 +#define APCI3120_TIMER_STATUS_REGISTER 0x0d +#define APCI3120_RD_STATUS 0x02 +#define APCI3120_WR_ADDRESS 0x00 +#define APCI3120_ENABLE_WATCHDOG 0x20 +#define APCI3120_DISABLE_WATCHDOG (~APCI3120_ENABLE_WATCHDOG) +#define APCI3120_ENABLE_TIMER_COUNTER 0x10 +#define APCI3120_DISABLE_TIMER_COUNTER (~APCI3120_ENABLE_TIMER_COUNTER) +#define APCI3120_FC_TIMER 0x1000 +#define APCI3120_ENABLE_TIMER0 0x1000 +#define APCI3120_ENABLE_TIMER1 0x2000 +#define APCI3120_ENABLE_TIMER2 0x4000 +#define APCI3120_DISABLE_TIMER0 (~APCI3120_ENABLE_TIMER0) +#define APCI3120_DISABLE_TIMER1 (~APCI3120_ENABLE_TIMER1) +#define APCI3120_DISABLE_TIMER2 (~APCI3120_ENABLE_TIMER2) + +#define APCI3120_TIMER2_SELECT_EOS 0xc0 +#define APCI3120_COUNTER 3 +#define APCI3120_DISABLE_ALL_TIMER (APCI3120_DISABLE_TIMER0 & \ + APCI3120_DISABLE_TIMER1 & \ + APCI3120_DISABLE_TIMER2) + +#define MAX_ANALOGINPUT_CHANNELS 32 + +struct str_AnalogReadInformation { + /* EOC or EOS */ + unsigned char b_Type; + /* Interrupt use or not */ + unsigned char b_InterruptFlag; + /* Selection of the conversion time */ + unsigned int ui_ConvertTiming; + /* Number of channel to read */ + unsigned char b_NbrOfChannel; + /* Number of the channel to be read */ + unsigned int ui_ChannelList[MAX_ANALOGINPUT_CHANNELS]; + /* Gain of each channel */ + unsigned int ui_RangeList[MAX_ANALOGINPUT_CHANNELS]; +}; + +/* ANALOG INPUT RANGE */ +static const struct comedi_lrange range_apci3120_ai = { + 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) + } +}; + +/* ANALOG OUTPUT RANGE */ +static const struct comedi_lrange range_apci3120_ao = { + 2, { + BIP_RANGE(10), + UNI_RANGE(10) + } +}; + + +/* FUNCTION DEFINITIONS */ + +/* ++----------------------------------------------------------------------------+ +| ANALOG INPUT SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ + +static int apci3120_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + unsigned int i; + + if ((data[0] != APCI3120_EOC_MODE) && (data[0] != APCI3120_EOS_MODE)) + return -1; + + /* Check for Conversion time to be added ?? */ + devpriv->ui_EocEosConversionTime = data[2]; + + if (data[0] == APCI3120_EOS_MODE) { + + /* Test the number of the channel */ + for (i = 0; i < data[3]; i++) { + + if (CR_CHAN(data[4 + i]) >= + this_board->i_NbrAiChannel) { + printk("bad channel list\n"); + return -2; + } + } + + devpriv->b_InterruptMode = APCI3120_EOS_MODE; + + if (data[1]) + devpriv->b_EocEosInterrupt = APCI3120_ENABLE; + else + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + /* Copy channel list and Range List to devpriv */ + + devpriv->ui_AiNbrofChannels = data[3]; + for (i = 0; i < devpriv->ui_AiNbrofChannels; i++) + devpriv->ui_AiChannelList[i] = data[4 + i]; + + } else { /* EOC */ + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + if (data[1]) + devpriv->b_EocEosInterrupt = APCI3120_ENABLE; + else + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + } + + return insn->n; +} + +/* + * This function will first check channel list is ok or not and then + * initialize the sequence RAM with the polarity, Gain,Channel number. + * If the last argument of function "check"is 1 then it only checks + * the channel list is ok or not. + */ +static int apci3120_setup_chan_list(struct comedi_device *dev, + struct comedi_subdevice *s, + int n_chan, + unsigned int *chanlist, + char check) +{ + struct addi_private *devpriv = dev->private; + unsigned int i; /* , differencial=0, bipolar=0; */ + unsigned int gain; + unsigned short us_TmpValue; + + /* correct channel and range number check itself comedi/range.c */ + if (n_chan < 1) { + if (!check) + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + /* All is ok, so we can setup channel/range list */ + if (check) + return 1; + + /* Code to set the PA and PR...Here it set PA to 0.. */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister & APCI3120_CLEAR_PA_PR; + devpriv->us_OutputRegister = ((n_chan - 1) & 0xf) << 8; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + + for (i = 0; i < n_chan; i++) { + /* store range list to card */ + us_TmpValue = CR_CHAN(chanlist[i]); /* get channel number; */ + + if (CR_RANGE(chanlist[i]) < APCI3120_BIPOLAR_RANGES) + us_TmpValue &= ((~APCI3120_UNIPOLAR) & 0xff); /* set bipolar */ + else + us_TmpValue |= APCI3120_UNIPOLAR; /* enable unipolar...... */ + + gain = CR_RANGE(chanlist[i]); /* get gain number */ + us_TmpValue |= ((gain & 0x03) << 4); /* <<4 for G0 and G1 bit in RAM */ + us_TmpValue |= i << 8; /* To select the RAM LOCATION.... */ + outw(us_TmpValue, dev->iobase + APCI3120_SEQ_RAM_ADDRESS); + + printk("\n Gain = %i", + (((unsigned char)CR_RANGE(chanlist[i]) & 0x03) << 2)); + printk("\n Channel = %i", CR_CHAN(chanlist[i])); + printk("\n Polarity = %i", us_TmpValue & APCI3120_UNIPOLAR); + } + return 1; /* we can serve this with scan logic */ +} + +/* + * Reads analog input in synchronous mode EOC and EOS is selected + * as per configured if no conversion time is set uses default + * conversion time 10 microsec. + */ +static int apci3120_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + unsigned short us_ConvertTiming, us_TmpValue, i; + unsigned char b_Tmp; + + /* fix conversion time to 10 us */ + if (!devpriv->ui_EocEosConversionTime) { + printk("No timer0 Value using 10 us\n"); + us_ConvertTiming = 10; + } else + us_ConvertTiming = (unsigned short) (devpriv->ui_EocEosConversionTime / 1000); /* nano to useconds */ + + /* this_board->ai_read(dev,us_ConvertTiming,insn->n,&insn->chanspec,data,insn->unused[0]); */ + + /* Clear software registers */ + devpriv->b_TimerSelectMode = 0; + devpriv->b_ModeSelectRegister = 0; + devpriv->us_OutputRegister = 0; +/* devpriv->b_DigitalOutputRegister=0; */ + + if (insn->unused[0] == 222) { /* second insn read */ + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ui_AiReadData[i]; + } else { + devpriv->tsk_Current = current; /* Save the current process task structure */ +/* + * Testing if board have the new Quartz and calculate the time value + * to set in the timer + */ + + us_TmpValue = + (unsigned short) inw(devpriv->iobase + APCI3120_RD_STATUS); + + /* EL250804: Testing if board APCI3120 have the new Quartz or if it is an APCI3001 */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + us_ConvertTiming = (us_ConvertTiming * 2) - 2; + } else { + us_ConvertTiming = + ((us_ConvertTiming * 12926) / 10000) - 1; + } + + us_TmpValue = (unsigned short) devpriv->b_InterruptMode; + + switch (us_TmpValue) { + + case APCI3120_EOC_MODE: + +/* + * Testing the interrupt flag and set the EOC bit Clears the FIFO + */ + inw(devpriv->iobase + APCI3120_RESET_FIFO); + + /* Initialize the sequence array */ + if (!apci3120_setup_chan_list(dev, s, 1, + &insn->chanspec, 0)) + return -EINVAL; + + /* Initialize Timer 0 mode 4 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | + APCI3120_TIMER_0_MODE_4; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + + /* Reset the scan bit and Disables the EOS, DMA, EOC interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_SCAN; + + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { + + /* Disables the EOS,DMA and enables the EOC interrupt */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_EOS_INT) | + APCI3120_ENABLE_EOC_INT; + inw(devpriv->iobase); + + } else { + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_ALL_INTERRUPT_WITHOUT_TIMER; + } + + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + /* Sets gate 0 */ + devpriv->us_OutputRegister = + (devpriv-> + us_OutputRegister & APCI3120_CLEAR_PA_PR) | + APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + /* Set the conversion time */ + outw(us_ConvertTiming, + devpriv->iobase + APCI3120_TIMER_VALUE); + + us_TmpValue = + (unsigned short) inw(dev->iobase + APCI3120_RD_STATUS); + + if (devpriv->b_EocEosInterrupt == APCI3120_DISABLE) { + + do { + /* Waiting for the end of conversion */ + us_TmpValue = + inw(devpriv->iobase + + APCI3120_RD_STATUS); + } while ((us_TmpValue & APCI3120_EOC) == + APCI3120_EOC); + + /* Read the result in FIFO and put it in insn data pointer */ + us_TmpValue = inw(devpriv->iobase + 0); + *data = us_TmpValue; + + inw(devpriv->iobase + APCI3120_RESET_FIFO); + } + + break; + + case APCI3120_EOS_MODE: + + inw(devpriv->iobase); + /* Clears the FIFO */ + inw(devpriv->iobase + APCI3120_RESET_FIFO); + /* clear PA PR and disable timer 0 */ + + devpriv->us_OutputRegister = + (devpriv-> + us_OutputRegister & APCI3120_CLEAR_PA_PR) | + APCI3120_DISABLE_TIMER0; + + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + if (!apci3120_setup_chan_list(dev, s, + devpriv->ui_AiNbrofChannels, + devpriv->ui_AiChannelList, 0)) + return -EINVAL; + + /* Initialize Timer 0 mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | + APCI3120_TIMER_0_MODE_2; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + /* Set the conversion time */ + outw(us_ConvertTiming, + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Set the scan bit */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | APCI3120_ENABLE_SCAN; + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + /* If Interrupt function is loaded */ + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { + /* Disables the EOC,DMA and enables the EOS interrupt */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_EOC_INT) | + APCI3120_ENABLE_EOS_INT; + inw(devpriv->iobase); + + } else + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_ALL_INTERRUPT_WITHOUT_TIMER; + + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + inw(devpriv->iobase + APCI3120_RD_STATUS); + + /* Sets gate 0 */ + + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister | APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Start conversion */ + outw(0, devpriv->iobase + APCI3120_START_CONVERSION); + + /* Waiting of end of conversion if interrupt is not installed */ + if (devpriv->b_EocEosInterrupt == APCI3120_DISABLE) { + /* Waiting the end of conversion */ + do { + us_TmpValue = + inw(devpriv->iobase + + APCI3120_RD_STATUS); + } while ((us_TmpValue & APCI3120_EOS) != + APCI3120_EOS); + + for (i = 0; i < devpriv->ui_AiNbrofChannels; + i++) { + /* Read the result in FIFO and write them in shared memory */ + us_TmpValue = inw(devpriv->iobase); + data[i] = (unsigned int) us_TmpValue; + } + + devpriv->b_InterruptMode = APCI3120_EOC_MODE; /* Restore defaults. */ + } + break; + + default: + printk("inputs wrong\n"); + + } + devpriv->ui_EocEosConversionTime = 0; /* re initializing the variable; */ + } + + return insn->n; + +} + +static int apci3120_reset(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + unsigned int i; + unsigned short us_TmpValue; + + devpriv->ai_running = 0; + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + devpriv->ui_EocEosConversionTime = 0; /* set eoc eos conv time to 0 */ + + /* variables used in timer subdevice */ + devpriv->b_Timer2Mode = 0; + devpriv->b_Timer2Interrupt = 0; + devpriv->b_ExttrigEnable = 0; /* Disable ext trigger */ + + /* Disable all interrupts, watchdog for the anolog output */ + devpriv->b_ModeSelectRegister = 0; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* Disables all counters, ext trigger and clears PA, PR */ + devpriv->us_OutputRegister = 0; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + +/* + * Code to set the all anolog o/p channel to 0v 8191 is decimal + * value for zero(0 v)volt in bipolar mode(default) + */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_1, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 1 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_2, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 2 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_3, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 3 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_4, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 4 */ + + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_5, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 5 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_6, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 6 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_7, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 7 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_8, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 8 */ + + /* Reset digital output to L0W */ + +/* ES05 outb(0x0,dev->iobase+APCI3120_DIGITAL_OUTPUT); */ + udelay(10); + + inw(dev->iobase + 0); /* make a dummy read */ + inb(dev->iobase + APCI3120_RESET_FIFO); /* flush FIFO */ + inw(dev->iobase + APCI3120_RD_STATUS); /* flush A/D status register */ + + /* code to reset the RAM sequence */ + for (i = 0; i < 16; i++) { + us_TmpValue = i << 8; /* select the location */ + outw(us_TmpValue, dev->iobase + APCI3120_SEQ_RAM_ADDRESS); + } + return 0; +} + +static int apci3120_exttrig_enable(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + + devpriv->us_OutputRegister |= APCI3120_ENABLE_EXT_TRIGGER; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + return 0; +} + +static int apci3120_exttrig_disable(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + + devpriv->us_OutputRegister &= ~APCI3120_ENABLE_EXT_TRIGGER; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + return 0; +} + +static int apci3120_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct addi_private *devpriv = dev->private; + + /* Disable A2P Fifo write and AMWEN signal */ + outw(0, devpriv->i_IobaseAddon + 4); + + /* Disable Bus Master ADD ON */ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(0, devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(0, devpriv->i_IobaseAddon + 2); + + /* Disable BUS Master PCI */ + outl(0, devpriv->i_IobaseAmcc + AMCC_OP_REG_MCSR); + + /* outl(inl(devpriv->i_IobaseAmcc+AMCC_OP_REG_INTCSR)&(~AINT_WRITE_COMPL), + * devpriv->i_IobaseAmcc+AMCC_OP_REG_INTCSR); stop amcc irqs */ + + /* outl(inl(devpriv->i_IobaseAmcc+AMCC_OP_REG_MCSR)&(~EN_A2P_TRANSFERS), + * devpriv->i_IobaseAmcc+AMCC_OP_REG_MCSR); stop DMA */ + + /* Disable ext trigger */ + apci3120_exttrig_disable(dev); + + devpriv->us_OutputRegister = 0; + /* stop counters */ + outw(devpriv-> + us_OutputRegister & APCI3120_DISABLE_TIMER0 & + APCI3120_DISABLE_TIMER1, dev->iobase + APCI3120_WR_ADDRESS); + + outw(APCI3120_DISABLE_ALL_TIMER, dev->iobase + APCI3120_WR_ADDRESS); + + /* DISABLE_ALL_INTERRUPT */ + outb(APCI3120_DISABLE_ALL_INTERRUPT, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + /* Flush FIFO */ + inb(dev->iobase + APCI3120_RESET_FIFO); + inw(dev->iobase + APCI3120_RD_STATUS); + devpriv->ui_AiActualScan = 0; + s->async->cur_chan = 0; + devpriv->ui_DmaActualBuffer = 0; + + devpriv->ai_running = 0; + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + apci3120_reset(dev); + return 0; +} + +static int apci3120_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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) /* Test Delay timing */ + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, 100000); + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->convert_arg) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + 10000); + } else { + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000); + } + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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 && + cmd->scan_begin_arg < cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = cmd->convert_arg * cmd->scan_end_arg; + err |= -EINVAL; + } + + if (err) + return 4; + + return 0; +} + +/* + * This is used for analog input cyclic acquisition. + * Performs the command operations. + * If DMA is configured does DMA initialization otherwise does the + * acquisition with EOS interrupt. + */ +static int apci3120_cyclic_ai(int mode, + struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned char b_Tmp; + unsigned int ui_Tmp, ui_DelayTiming = 0, ui_TimerValue1 = 0, dmalen0 = + 0, dmalen1 = 0, ui_TimerValue2 = + 0, ui_TimerValue0, ui_ConvertTiming; + unsigned short us_TmpValue; + + /*******************/ + /* Resets the FIFO */ + /*******************/ + inb(dev->iobase + APCI3120_RESET_FIFO); + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* inw(dev->iobase+APCI3120_RD_STATUS); */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + devpriv->ai_running = 1; + + /* clear software registers */ + devpriv->b_TimerSelectMode = 0; + devpriv->us_OutputRegister = 0; + devpriv->b_ModeSelectRegister = 0; + /* devpriv->b_DigitalOutputRegister=0; */ + + /* COMMENT JK 07.05.04: Followings calls are in i_APCI3120_StartAnalogInputAcquisition */ + + /****************************/ + /* Clear Timer Write TC int */ + /****************************/ + outl(APCI3120_CLEAR_WRITE_TC_INT, + devpriv->i_IobaseAmcc + APCI3120_AMCC_OP_REG_INTCSR); + + /************************************/ + /* Clears the timer status register */ + /************************************/ + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* inw(dev->iobase+APCI3120_TIMER_STATUS_REGISTER); */ + /* inb(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /**************************/ + /* Disables All Timer */ + /* Sets PR and PA to 0 */ + /**************************/ + devpriv->us_OutputRegister = devpriv->us_OutputRegister & + APCI3120_DISABLE_TIMER0 & + APCI3120_DISABLE_TIMER1 & APCI3120_CLEAR_PA_PR; + + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + + /*******************/ + /* Resets the FIFO */ + /*******************/ + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + inb(devpriv->iobase + APCI3120_RESET_FIFO); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + devpriv->ui_AiActualScan = 0; + s->async->cur_chan = 0; + devpriv->ui_DmaActualBuffer = 0; + + /* value for timer2 minus -2 has to be done .....dunno y?? */ + ui_TimerValue2 = cmd->stop_arg - 2; + ui_ConvertTiming = cmd->convert_arg; + + if (mode == 2) + ui_DelayTiming = cmd->scan_begin_arg; + + /**********************************/ + /* Initializes the sequence array */ + /**********************************/ + if (!apci3120_setup_chan_list(dev, s, devpriv->ui_AiNbrofChannels, + cmd->chanlist, 0)) + return -EINVAL; + + us_TmpValue = (unsigned short) inw(dev->iobase + APCI3120_RD_STATUS); +/*** EL241003 : add this section in comment because floats must not be used + if((us_TmpValue & 0x00B0)==0x00B0) + { + f_ConvertValue=(((float)ui_ConvertTiming * 0.002) - 2); + ui_TimerValue0=(unsigned int)f_ConvertValue; + if (mode==2) + { + f_DelayValue = (((float)ui_DelayTiming * 0.00002) - 2); + ui_TimerValue1 = (unsigned int) f_DelayValue; + } + } + else + { + f_ConvertValue=(((float)ui_ConvertTiming * 0.0012926) - 1); + ui_TimerValue0=(unsigned int)f_ConvertValue; + if (mode == 2) + { + f_DelayValue = (((float)ui_DelayTiming * 0.000012926) - 1); + ui_TimerValue1 = (unsigned int) f_DelayValue; + } + } +***********************************************************************************************/ +/*** EL241003 Begin : add this section to replace floats calculation by integer calculations **/ + /* EL250804: Testing if board APCI3120 have the new Quartz or if it is an APCI3001 */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + ui_TimerValue0 = ui_ConvertTiming * 2 - 2000; + ui_TimerValue0 = ui_TimerValue0 / 1000; + + if (mode == 2) { + ui_DelayTiming = ui_DelayTiming / 1000; + ui_TimerValue1 = ui_DelayTiming * 2 - 200; + ui_TimerValue1 = ui_TimerValue1 / 100; + } + } else { + ui_ConvertTiming = ui_ConvertTiming / 1000; + ui_TimerValue0 = ui_ConvertTiming * 12926 - 10000; + ui_TimerValue0 = ui_TimerValue0 / 10000; + + if (mode == 2) { + ui_DelayTiming = ui_DelayTiming / 1000; + ui_TimerValue1 = ui_DelayTiming * 12926 - 1; + ui_TimerValue1 = ui_TimerValue1 / 1000000; + } + } +/*** EL241003 End ******************************************************************************/ + + if (devpriv->b_ExttrigEnable == APCI3120_ENABLE) + apci3120_exttrig_enable(dev); /* activate EXT trigger */ + switch (mode) { + case 1: + /* init timer0 in mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | APCI3120_TIMER_0_MODE_2; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + /* Set the conversion time */ + outw(((unsigned short) ui_TimerValue0), + dev->iobase + APCI3120_TIMER_VALUE); + break; + + case 2: + /* init timer1 in mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xF3) | APCI3120_TIMER_1_MODE_2; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 1 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_1_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + /* Set the conversion time */ + outw(((unsigned short) ui_TimerValue1), + dev->iobase + APCI3120_TIMER_VALUE); + + /* init timer0 in mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | APCI3120_TIMER_0_MODE_2; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + + /* Set the conversion time */ + outw(((unsigned short) ui_TimerValue0), + dev->iobase + APCI3120_TIMER_VALUE); + break; + + } + /* ##########common for all modes################# */ + + /***********************/ + /* Clears the SCAN bit */ + /***********************/ + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* devpriv->b_ModeSelectRegister=devpriv->b_ModeSelectRegister | APCI3120_DISABLE_SCAN; */ + + devpriv->b_ModeSelectRegister = devpriv->b_ModeSelectRegister & + APCI3120_DISABLE_SCAN; + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* If DMA is disabled */ + if (devpriv->us_UseDma == APCI3120_DISABLE) { + /* disable EOC and enable EOS */ + devpriv->b_InterruptMode = APCI3120_EOS_MODE; + devpriv->b_EocEosInterrupt = APCI3120_ENABLE; + + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOC_INT) | + APCI3120_ENABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + if (cmd->stop_src == TRIG_COUNT) { +/* + * configure Timer2 For counting EOS Reset gate 2 of Timer 2 to + * disable it (Set Bit D14 to 0) + */ + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister & APCI3120_DISABLE_TIMER2; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + + /* DISABLE TIMER intERRUPT */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_INT & 0xEF; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* (1) Init timer 2 in mode 0 and write timer value */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0x0F) | + APCI3120_TIMER_2_MODE_0; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + outw(LOWORD(ui_TimerValue2), + dev->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + outw(HIWORD(ui_TimerValue2), + dev->iobase + APCI3120_TIMER_VALUE); + + /* (2) Reset FC_TIMER BIT Clearing timer status register */ + inb(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); + /* enable timer counter and disable watch dog */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_TIMER_COUNTER) & + APCI3120_DISABLE_WATCHDOG; + /* select EOS clock input for timer 2 */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_TIMER2_SELECT_EOS; + /* Enable timer2 interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_TIMER_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + devpriv->b_Timer2Mode = APCI3120_COUNTER; + devpriv->b_Timer2Interrupt = APCI3120_ENABLE; + } + } else { + /* If DMA Enabled */ + unsigned int scan_bytes = cmd->scan_end_arg * sizeof(short); + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* inw(dev->iobase+0); reset EOC bit */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + devpriv->b_InterruptMode = APCI3120_DMA_MODE; + + /************************************/ + /* Disables the EOC, EOS interrupt */ + /************************************/ + devpriv->b_ModeSelectRegister = devpriv->b_ModeSelectRegister & + APCI3120_DISABLE_EOC_INT & APCI3120_DISABLE_EOS_INT; + + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + dmalen0 = devpriv->ui_DmaBufferSize[0]; + dmalen1 = devpriv->ui_DmaBufferSize[1]; + + 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 & TRIG_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; + } + devpriv->ui_DmaBufferUsesize[0] = dmalen0; + devpriv->ui_DmaBufferUsesize[1] = dmalen1; + + /* Initialize DMA */ + +/* + * Set Transfer count enable bit and A2P_fifo reset bit in AGCSTS + * register 1 + */ + ui_Tmp = AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO; + outl(ui_Tmp, devpriv->i_IobaseAmcc + AMCC_OP_REG_AGCSTS); + + /* changed since 16 bit interface for add on */ + /*********************/ + /* ENABLE BUS MASTER */ + /*********************/ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_LOW, + devpriv->i_IobaseAddon + 2); + + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH, + devpriv->i_IobaseAddon + 2); + +/* + * TO VERIFIED BEGIN JK 07.05.04: Comparison between WIN32 and Linux + * driver + */ + outw(0x1000, devpriv->i_IobaseAddon + 2); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /* 2 No change */ + /* A2P FIFO MANAGEMENT */ + /* A2P fifo reset & transfer control enable */ + + /***********************/ + /* A2P FIFO MANAGEMENT */ + /***********************/ + outl(APCI3120_A2P_FIFO_MANAGEMENT, devpriv->i_IobaseAmcc + + APCI3120_AMCC_OP_MCSR); + +/* + * 3 + * beginning address of dma buf The 32 bit address of dma buffer + * is converted into two 16 bit addresses Can done by using _attach + * and put into into an array array used may be for differnet pages + */ + + /* DMA Start Address Low */ + outw(APCI3120_ADD_ON_MWAR_LOW, devpriv->i_IobaseAddon + 0); + outw((devpriv->ul_DmaBufferHw[0] & 0xFFFF), + devpriv->i_IobaseAddon + 2); + + /*************************/ + /* DMA Start Address High */ + /*************************/ + outw(APCI3120_ADD_ON_MWAR_HIGH, devpriv->i_IobaseAddon + 0); + outw((devpriv->ul_DmaBufferHw[0] / 65536), + devpriv->i_IobaseAddon + 2); + +/* + * 4 + * amount of bytes to be transferred set transfer count used ADDON + * MWTC register commented testing + * outl(devpriv->ui_DmaBufferUsesize[0], + * devpriv->i_IobaseAddon+AMCC_OP_REG_AMWTC); + */ + + /**************************/ + /* Nbr of acquisition LOW */ + /**************************/ + outw(APCI3120_ADD_ON_MWTC_LOW, devpriv->i_IobaseAddon + 0); + outw((devpriv->ui_DmaBufferUsesize[0] & 0xFFFF), + devpriv->i_IobaseAddon + 2); + + /***************************/ + /* Nbr of acquisition HIGH */ + /***************************/ + outw(APCI3120_ADD_ON_MWTC_HIGH, devpriv->i_IobaseAddon + 0); + outw((devpriv->ui_DmaBufferUsesize[0] / 65536), + devpriv->i_IobaseAddon + 2); + +/* + * 5 + * To configure A2P FIFO testing outl( + * FIFO_ADVANCE_ON_BYTE_2,devpriv->i_IobaseAmcc+AMCC_OP_REG_INTCSR); + */ + + /******************/ + /* A2P FIFO RESET */ + /******************/ +/* + * TO VERIFY BEGIN JK 07.05.04: Comparison between WIN32 and Linux + * driver + */ + outl(0x04000000UL, devpriv->i_IobaseAmcc + AMCC_OP_REG_MCSR); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + +/* + * 6 + * ENABLE A2P FIFO WRITE AND ENABLE AMWEN AMWEN_ENABLE | + * A2P_FIFO_WRITE_ENABLE (0x01|0x02)=0x03 + */ + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* outw(3,devpriv->i_IobaseAddon + 4); */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + +/* + * 7 + * initialise end of dma interrupt AINT_WRITE_COMPL = + * ENABLE_WRITE_TC_INT(ADDI) + */ + /***************************************************/ + /* A2P FIFO CONFIGURATE, END OF DMA intERRUPT INIT */ + /***************************************************/ + outl((APCI3120_FIFO_ADVANCE_ON_BYTE_2 | + APCI3120_ENABLE_WRITE_TC_INT), + devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /******************************************/ + /* ENABLE A2P FIFO WRITE AND ENABLE AMWEN */ + /******************************************/ + outw(3, devpriv->i_IobaseAddon + 4); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /******************/ + /* A2P FIFO RESET */ + /******************/ + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + outl(0x04000000UL, + devpriv->i_IobaseAmcc + APCI3120_AMCC_OP_MCSR); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + } + + if (devpriv->us_UseDma == APCI3120_DISABLE && + cmd->stop_src == TRIG_COUNT) { + /* set gate 2 to start conversion */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER2; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + } + + switch (mode) { + case 1: + /* set gate 0 to start conversion */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + break; + case 2: + /* set gate 0 and gate 1 */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER1; + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + break; + + } + + return 0; + +} + +/* + * Does asynchronous acquisition. + * Determines the mode 1 or 2. + */ +static int apci3120_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct addi_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + /* loading private structure with cmd structure inputs */ + devpriv->ui_AiNbrofChannels = cmd->chanlist_len; + + if (cmd->start_src == TRIG_EXT) + devpriv->b_ExttrigEnable = APCI3120_ENABLE; + else + devpriv->b_ExttrigEnable = APCI3120_DISABLE; + + if (cmd->scan_begin_src == TRIG_FOLLOW) + return apci3120_cyclic_ai(1, dev, s); + else /* TRIG_TIMER */ + return apci3120_cyclic_ai(2, dev, s); +} + +/* + * This function copies the data from DMA buffer to the Comedi buffer. + */ +static void v_APCI3120_InterruptDmaMoveBlock16bit(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *dma_buffer, + unsigned int num_samples) +{ + struct addi_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + devpriv->ui_AiActualScan += + (s->async->cur_chan + num_samples) / cmd->scan_end_arg; + s->async->cur_chan += num_samples; + s->async->cur_chan %= cmd->scan_end_arg; + + cfc_write_array_to_buffer(s, dma_buffer, num_samples * sizeof(short)); +} + +/* + * This is a handler for the DMA interrupt. + * This function copies the data to Comedi Buffer. + * For continuous DMA it reinitializes the DMA operation. + * For single mode DMA it stop the acquisition. + */ +static void apci3120_interrupt_dma(int irq, void *d) +{ + struct comedi_device *dev = d; + struct addi_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int next_dma_buf, samplesinbuf; + unsigned long low_word, high_word, var; + unsigned int ui_Tmp; + + samplesinbuf = + devpriv->ui_DmaBufferUsesize[devpriv->ui_DmaActualBuffer] - + inl(devpriv->i_IobaseAmcc + AMCC_OP_REG_MWTC); + + if (samplesinbuf < + devpriv->ui_DmaBufferUsesize[devpriv->ui_DmaActualBuffer]) { + comedi_error(dev, "Interrupted DMA transfer!"); + } + if (samplesinbuf & 1) { + comedi_error(dev, "Odd count of bytes in DMA ring!"); + apci3120_cancel(dev, s); + return; + } + samplesinbuf = samplesinbuf >> 1; /* number of received samples */ + if (devpriv->b_DmaDoubleBuffer) { + /* switch DMA buffers if is used double buffering */ + next_dma_buf = 1 - devpriv->ui_DmaActualBuffer; + + ui_Tmp = AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO; + outl(ui_Tmp, devpriv->i_IobaseAddon + AMCC_OP_REG_AGCSTS); + + /* changed since 16 bit interface for add on */ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_LOW, + devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH, devpriv->i_IobaseAddon + 2); /* 0x1000 is out putted in windows driver */ + + var = devpriv->ul_DmaBufferHw[next_dma_buf]; + low_word = var & 0xffff; + var = devpriv->ul_DmaBufferHw[next_dma_buf]; + high_word = var / 65536; + + /* DMA Start Address Low */ + outw(APCI3120_ADD_ON_MWAR_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + + /* DMA Start Address High */ + outw(APCI3120_ADD_ON_MWAR_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + + var = devpriv->ui_DmaBufferUsesize[next_dma_buf]; + low_word = var & 0xffff; + var = devpriv->ui_DmaBufferUsesize[next_dma_buf]; + high_word = var / 65536; + + /* Nbr of acquisition LOW */ + outw(APCI3120_ADD_ON_MWTC_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + + /* Nbr of acquisition HIGH */ + outw(APCI3120_ADD_ON_MWTC_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + +/* + * To configure A2P FIFO + * ENABLE A2P FIFO WRITE AND ENABLE AMWEN + * AMWEN_ENABLE | A2P_FIFO_WRITE_ENABLE (0x01|0x02)=0x03 + */ + outw(3, devpriv->i_IobaseAddon + 4); + /* initialise end of dma interrupt AINT_WRITE_COMPL = ENABLE_WRITE_TC_INT(ADDI) */ + outl((APCI3120_FIFO_ADVANCE_ON_BYTE_2 | + APCI3120_ENABLE_WRITE_TC_INT), + devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); + + } + if (samplesinbuf) { + v_APCI3120_InterruptDmaMoveBlock16bit(dev, s, + devpriv->ul_DmaBufferVirtual[devpriv-> + ui_DmaActualBuffer], samplesinbuf); + + if (!(cmd->flags & TRIG_WAKE_EOS)) { + s->async->events |= COMEDI_CB_EOS; + comedi_event(dev, s); + } + } + if (cmd->stop_src == TRIG_COUNT) + if (devpriv->ui_AiActualScan >= cmd->stop_arg) { + /* all data sampled */ + apci3120_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + + if (devpriv->b_DmaDoubleBuffer) { /* switch dma buffers */ + devpriv->ui_DmaActualBuffer = 1 - devpriv->ui_DmaActualBuffer; + } else { +/* + * restart DMA if is not used double buffering + * ADDED REINITIALISE THE DMA + */ + ui_Tmp = AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO; + outl(ui_Tmp, devpriv->i_IobaseAddon + AMCC_OP_REG_AGCSTS); + + /* changed since 16 bit interface for add on */ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_LOW, + devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH, devpriv->i_IobaseAddon + 2); /* */ +/* + * A2P FIFO MANAGEMENT + * A2P fifo reset & transfer control enable + */ + outl(APCI3120_A2P_FIFO_MANAGEMENT, + devpriv->i_IobaseAmcc + AMCC_OP_REG_MCSR); + + var = devpriv->ul_DmaBufferHw[0]; + low_word = var & 0xffff; + var = devpriv->ul_DmaBufferHw[0]; + high_word = var / 65536; + outw(APCI3120_ADD_ON_MWAR_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_MWAR_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + + var = devpriv->ui_DmaBufferUsesize[0]; + low_word = var & 0xffff; /* changed */ + var = devpriv->ui_DmaBufferUsesize[0]; + high_word = var / 65536; + outw(APCI3120_ADD_ON_MWTC_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_MWTC_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + +/* + * To configure A2P FIFO + * ENABLE A2P FIFO WRITE AND ENABLE AMWEN + * AMWEN_ENABLE | A2P_FIFO_WRITE_ENABLE (0x01|0x02)=0x03 + */ + outw(3, devpriv->i_IobaseAddon + 4); + /* initialise end of dma interrupt AINT_WRITE_COMPL = ENABLE_WRITE_TC_INT(ADDI) */ + outl((APCI3120_FIFO_ADVANCE_ON_BYTE_2 | + APCI3120_ENABLE_WRITE_TC_INT), + devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); + } +} + +/* + * This function handles EOS interrupt. + * This function copies the acquired data(from FIFO) to Comedi buffer. + */ +static int apci3120_interrupt_handle_eos(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + int n_chan, i; + int err = 1; + + n_chan = devpriv->ui_AiNbrofChannels; + + for (i = 0; i < n_chan; i++) + err &= comedi_buf_put(s, inw(dev->iobase + 0)); + + s->async->events |= COMEDI_CB_EOS; + + if (err == 0) + s->async->events |= COMEDI_CB_OVERFLOW; + + comedi_event(dev, s); + + return 0; +} + +static void apci3120_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct addi_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned short int_daq; + unsigned int int_amcc, ui_Check, i; + unsigned short us_TmpValue; + unsigned char b_DummyRead; + + ui_Check = 1; + + int_daq = inw(dev->iobase + APCI3120_RD_STATUS) & 0xf000; /* get IRQ reasons */ + int_amcc = inl(devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); /* get AMCC int register */ + + if ((!int_daq) && (!(int_amcc & ANY_S593X_INT))) { + comedi_error(dev, "IRQ from unknown source"); + return; + } + + outl(int_amcc | 0x00ff0000, devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); /* shutdown IRQ reasons in AMCC */ + + int_daq = (int_daq >> 12) & 0xF; + + if (devpriv->b_ExttrigEnable == APCI3120_ENABLE) { + /* Disable ext trigger */ + apci3120_exttrig_disable(dev); + devpriv->b_ExttrigEnable = APCI3120_DISABLE; + } + /* clear the timer 2 interrupt */ + inb(devpriv->i_IobaseAmcc + APCI3120_TIMER_STATUS_REGISTER); + + if (int_amcc & MASTER_ABORT_INT) + comedi_error(dev, "AMCC IRQ - MASTER DMA ABORT!"); + if (int_amcc & TARGET_ABORT_INT) + comedi_error(dev, "AMCC IRQ - TARGET DMA ABORT!"); + + /* Ckeck if EOC interrupt */ + if (((int_daq & 0x8) == 0) + && (devpriv->b_InterruptMode == APCI3120_EOC_MODE)) { + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { + + /* Read the AI Value */ + + devpriv->ui_AiReadData[0] = + (unsigned int) inw(devpriv->iobase + 0); + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } else { + /* Disable EOC Interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOC_INT; + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + } + } + + /* Check If EOS interrupt */ + if ((int_daq & 0x2) && (devpriv->b_InterruptMode == APCI3120_EOS_MODE)) { + + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { /* enable this in without DMA ??? */ + + if (devpriv->ai_running) { + ui_Check = 0; + apci3120_interrupt_handle_eos(dev); + devpriv->ui_AiActualScan++; + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + + APCI3120_WRITE_MODE_SELECT); + } else { + ui_Check = 0; + for (i = 0; i < devpriv->ui_AiNbrofChannels; + i++) { + us_TmpValue = inw(devpriv->iobase + 0); + devpriv->ui_AiReadData[i] = + (unsigned int) us_TmpValue; + } + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + + } + + } else { + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; /* Default settings */ + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + } + + } + /* Timer2 interrupt */ + if (int_daq & 0x1) { + + switch (devpriv->b_Timer2Mode) { + case APCI3120_COUNTER: + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* stop timer 2 */ + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister & APCI3120_DISABLE_ALL_TIMER; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + + /* stop timer 0 and timer 1 */ + apci3120_cancel(dev, s); + + /* UPDATE-0.7.57->0.7.68comedi_done(dev,s); */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + + break; + + case APCI3120_TIMER: + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + break; + + case APCI3120_WATCHDOG: + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + break; + + default: + + /* disable Timer Interrupt */ + + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_INT; + + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + } + + b_DummyRead = inb(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); + + } + + if ((int_daq & 0x4) && (devpriv->b_InterruptMode == APCI3120_DMA_MODE)) { + if (devpriv->ai_running) { + + /****************************/ + /* Clear Timer Write TC int */ + /****************************/ + + outl(APCI3120_CLEAR_WRITE_TC_INT, + devpriv->i_IobaseAmcc + + APCI3120_AMCC_OP_REG_INTCSR); + + /************************************/ + /* Clears the timer status register */ + /************************************/ + inw(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); + /* do some data transfer */ + apci3120_interrupt_dma(irq, d); + } else { + /* Stops the Timer */ + outw(devpriv-> + us_OutputRegister & APCI3120_DISABLE_TIMER0 & + APCI3120_DISABLE_TIMER1, + dev->iobase + APCI3120_WR_ADDRESS); + } + + } + + return; +} + +/* + * Configure Timer 2 + * + * data[0] = TIMER configure as timer + * = WATCHDOG configure as watchdog + * data[1] = Timer constant + * data[2] = Timer2 interrupt (1)enable or(0) disable + */ +static int apci3120_config_insn_timer(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + unsigned int ui_Timervalue2; + unsigned short us_TmpValue; + unsigned char b_Tmp; + + if (!data[1]) + comedi_error(dev, "config:No timer constant !"); + + devpriv->b_Timer2Interrupt = (unsigned char) data[2]; /* save info whether to enable or disable interrupt */ + + ui_Timervalue2 = data[1] / 1000; /* convert nano seconds to u seconds */ + + /* this_board->timer_config(dev, ui_Timervalue2,(unsigned char)data[0]); */ + us_TmpValue = (unsigned short) inw(devpriv->iobase + APCI3120_RD_STATUS); + +/* + * EL250804: Testing if board APCI3120 have the new Quartz or if it + * is an APCI3001 and calculate the time value to set in the timer + */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 50; + } else { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 70; + } + + /* Reset gate 2 of Timer 2 to disable it (Set Bit D14 to 0) */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister & APCI3120_DISABLE_TIMER2; + outw(devpriv->us_OutputRegister, devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Disable TIMER Interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_TIMER_INT & 0xEF; + + /* Disable Eoc and Eos Interrupts */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOC_INT & + APCI3120_DISABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + if (data[0] == APCI3120_TIMER) { /* initialize timer */ + /* devpriv->b_ModeSelectRegister=devpriv->b_ModeSelectRegister | + * APCI3120_ENABLE_TIMER_INT; */ + + /* outb(devpriv->b_ModeSelectRegister,devpriv->iobase+APCI3120_WRITE_MODE_SELECT); */ + + /* Set the Timer 2 in mode 2(Timer) */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0x0F) | APCI3120_TIMER_2_MODE_2; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + +/* + * Configure the timer 2 for writing the LOW unsigned short of timer + * is Delay value You must make a b_tmp variable with + * DigitalOutPutRegister because at Address_1+APCI3120_TIMER_CRT0 + * you can set the digital output and configure the timer 2,and if + * you don't make this, digital output are erase (Set to 0) + */ + + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + outw(LOWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + outw(HIWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + /* timer2 in Timer mode enabled */ + devpriv->b_Timer2Mode = APCI3120_TIMER; + + } else { /* Initialize Watch dog */ + + /* Set the Timer 2 in mode 5(Watchdog) */ + + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0x0F) | APCI3120_TIMER_2_MODE_5; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + +/* + * Configure the timer 2 for writing the LOW unsigned short of timer + * is Delay value You must make a b_tmp variable with + * DigitalOutPutRegister because at Address_1+APCI3120_TIMER_CRT0 + * you can set the digital output and configure the timer 2,and if + * you don't make this, digital output are erase (Set to 0) + */ + + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + outw(LOWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + outw(HIWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + /* watchdog enabled */ + devpriv->b_Timer2Mode = APCI3120_WATCHDOG; + + } + + return insn->n; + +} + +/* + * To start and stop the timer + * + * data[0] = 1 (start) + * = 0 (stop) + * = 2 (write new value) + * data[1] = new value + * + * devpriv->b_Timer2Mode = 0 DISABLE + * = 1 Timer + * = 2 Watch dog + */ +static int apci3120_write_insn_timer(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct addi_board *this_board = comedi_board(dev); + struct addi_private *devpriv = dev->private; + unsigned int ui_Timervalue2 = 0; + unsigned short us_TmpValue; + unsigned char b_Tmp; + + if ((devpriv->b_Timer2Mode != APCI3120_WATCHDOG) + && (devpriv->b_Timer2Mode != APCI3120_TIMER)) { + comedi_error(dev, "\nwrite:timer2 not configured "); + return -EINVAL; + } + + if (data[0] == 2) { /* write new value */ + if (devpriv->b_Timer2Mode != APCI3120_TIMER) { + comedi_error(dev, + "write :timer2 not configured in TIMER MODE"); + return -EINVAL; + } + + if (data[1]) + ui_Timervalue2 = data[1]; + else + ui_Timervalue2 = 0; + } + + /* this_board->timer_write(dev,data[0],ui_Timervalue2); */ + + switch (data[0]) { + case APCI3120_START: + + /* Reset FC_TIMER BIT */ + inb(devpriv->iobase + APCI3120_TIMER_STATUS_REGISTER); + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { /* start timer */ + /* Enable Timer */ + devpriv->b_ModeSelectRegister = + devpriv->b_ModeSelectRegister & 0x0B; + } else { /* start watch dog */ + /* Enable WatchDog */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & 0x0B) | + APCI3120_ENABLE_WATCHDOG; + } + + /* enable disable interrupt */ + if ((devpriv->b_Timer2Interrupt) == APCI3120_ENABLE) { + + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_TIMER_INT; + /* save the task structure to pass info to user */ + devpriv->tsk_Current = current; + } else { + + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_INT; + } + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { /* start timer */ + /* For Timer mode is Gate2 must be activated **timer started */ + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister | APCI3120_ENABLE_TIMER2; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + } + + break; + + case APCI3120_STOP: + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { + /* Disable timer */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_COUNTER; + } else { + /* Disable WatchDog */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_WATCHDOG; + } + /* Disable timer interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_TIMER_INT; + + /* Write above states to register */ + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + /* Reset Gate 2 */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister & APCI3120_DISABLE_TIMER_INT; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Reset FC_TIMER BIT */ + inb(devpriv->iobase + APCI3120_TIMER_STATUS_REGISTER); + + /* Disable timer */ + /* devpriv->b_Timer2Mode=APCI3120_DISABLE; */ + + break; + + case 2: /* write new value to Timer */ + if (devpriv->b_Timer2Mode != APCI3120_TIMER) { + comedi_error(dev, + "write :timer2 not configured in TIMER MODE"); + return -EINVAL; + } + /* ui_Timervalue2=data[1]; // passed as argument */ + us_TmpValue = + (unsigned short) inw(devpriv->iobase + APCI3120_RD_STATUS); + +/* + * EL250804: Testing if board APCI3120 have the new Quartz or if it + * is an APCI3001 and calculate the time value to set in the timer + */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 50; + } else { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 70; + } + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + outw(LOWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + outw(HIWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + break; + default: + return -EINVAL; /* Not a valid input */ + } + + return insn->n; +} + +/* + * Read the Timer value + * + * for Timer: data[0]= Timer constant + * + * for watchdog: data[0] = 0 (still running) + * = 1 (run down) + */ +static int apci3120_read_insn_timer(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned char b_Tmp; + unsigned short us_TmpValue, us_TmpValue_2, us_StatusValue; + + if ((devpriv->b_Timer2Mode != APCI3120_WATCHDOG) + && (devpriv->b_Timer2Mode != APCI3120_TIMER)) { + comedi_error(dev, "\nread:timer2 not configured "); + } + + /* this_board->timer_read(dev,data); */ + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { + + /* Read the LOW unsigned short of Timer 2 register */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + us_TmpValue = inw(devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Read the HIGH unsigned short of Timer 2 register */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + us_TmpValue_2 = inw(devpriv->iobase + APCI3120_TIMER_VALUE); + + /* combining both words */ + data[0] = (unsigned int) ((us_TmpValue) | ((us_TmpValue_2) << 16)); + + } else { /* Read watch dog status */ + + us_StatusValue = inw(devpriv->iobase + APCI3120_RD_STATUS); + us_StatusValue = + ((us_StatusValue & APCI3120_FC_TIMER) >> 12) & 1; + if (us_StatusValue == 1) { + /* RESET FC_TIMER BIT */ + inb(devpriv->iobase + APCI3120_TIMER_STATUS_REGISTER); + } + data[0] = us_StatusValue; /* when data[0] = 1 then the watch dog has rundown */ + } + return insn->n; +} + +static int apci3120_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int val; + + /* the input channels are bits 11:8 of the status reg */ + val = inw(devpriv->iobase + APCI3120_RD_STATUS); + data[1] = (val >> 8) & 0xf; + + 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 addi_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) { + /* The do channels are bits 7:4 of the do register */ + devpriv->b_DigitalOutputRegister = s->state << 4; + + outb(devpriv->b_DigitalOutputRegister, + devpriv->iobase + APCI3120_DIGITAL_OUTPUT); + } + + data[1] = s->state; + + return insn->n; +} + +static int apci3120_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Range, ui_Channel; + unsigned short us_TmpValue; + + ui_Range = CR_RANGE(insn->chanspec); + ui_Channel = CR_CHAN(insn->chanspec); + + /* this_board->ao_write(dev, ui_Range, ui_Channel,data[0]); */ + if (ui_Range) { /* if 1 then unipolar */ + + if (data[0] != 0) + data[0] = + ((((ui_Channel & 0x03) << 14) & 0xC000) | (1 << + 13) | (data[0] + 8191)); + else + data[0] = + ((((ui_Channel & 0x03) << 14) & 0xC000) | (1 << + 13) | 8192); + + } else { /* if 0 then bipolar */ + data[0] = + ((((ui_Channel & 0x03) << 14) & 0xC000) | (0 << 13) | + data[0]); + + } + +/* + * out put n values at the given channel. printk("\nwaiting for + * DA_READY BIT"); + */ + do { /* Waiting of DA_READY BIT */ + us_TmpValue = + ((unsigned short) inw(devpriv->iobase + + APCI3120_RD_STATUS)) & 0x0001; + } while (us_TmpValue != 0x0001); + + if (ui_Channel <= 3) +/* + * for channel 0-3 out at the register 1 (wrDac1-8) data[i] + * typecasted to ushort since word write is to be done + */ + outw((unsigned short) data[0], + devpriv->iobase + APCI3120_ANALOG_OUTPUT_1); + else +/* + * for channel 4-7 out at the register 2 (wrDac5-8) data[i] + * typecasted to ushort since word write is to be done + */ + outw((unsigned short) data[0], + devpriv->iobase + APCI3120_ANALOG_OUTPUT_2); + + return insn->n; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c new file mode 100644 index 00000000000..f540394d17b --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c @@ -0,0 +1,3165 @@ +/** +@verbatim + +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. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-3200 | Compiler : GCC | + | Module name : hwdrv_apci3200.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-3200 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | 02.07.04 | J. Krauth | Modification from the driver in order to | + | | | correct some errors when using several boards. | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ + | 26.10.04 | J. Krauth | - Update for COMEDI 0.7.68 | + | | | - Read eeprom value | + | | | - Append APCI-3300 | + +----------+-----------+------------------------------------------------+ +*/ + +/* #define PRINT_INFO */ + +/* Card Specific information */ +/* #define APCI3200_ADDRESS_RANGE 264 */ + +/* Analog Input related Defines */ +#define APCI3200_AI_OFFSET_GAIN 0 +#define APCI3200_AI_SC_TEST 4 +#define APCI3200_AI_IRQ 8 +#define APCI3200_AI_AUTOCAL 12 +#define APCI3200_RELOAD_CONV_TIME_VAL 32 +#define APCI3200_CONV_TIME_TIME_BASE 36 +#define APCI3200_RELOAD_DELAY_TIME_VAL 40 +#define APCI3200_DELAY_TIME_TIME_BASE 44 +#define APCI3200_AI_MODULE1 0 +#define APCI3200_AI_MODULE2 64 +#define APCI3200_AI_MODULE3 128 +#define APCI3200_AI_MODULE4 192 +#define TRUE 1 +#define FALSE 0 +#define APCI3200_AI_EOSIRQ 16 +#define APCI3200_AI_EOS 20 +#define APCI3200_AI_CHAN_ID 24 +#define APCI3200_AI_CHAN_VAL 28 +#define ANALOG_INPUT 0 +#define TEMPERATURE 1 +#define RESISTANCE 2 + +#define ENABLE_EXT_TRIG 1 +#define ENABLE_EXT_GATE 2 +#define ENABLE_EXT_TRIG_GATE 3 + +#define APCI3200_MAXVOLT 2.5 +#define ADDIDATA_GREATER_THAN_TEST 0 +#define ADDIDATA_LESS_THAN_TEST 1 + +#define ADDIDATA_UNIPOLAR 1 +#define ADDIDATA_BIPOLAR 2 + +#define MAX_MODULE 4 + +/* ANALOG INPUT RANGE */ +static const struct comedi_lrange range_apci3200_ai = { + 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 range_apci3300_ai = { + 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1) + } +}; + +int MODULE_NO; +struct { + int i_Gain; + int i_Polarity; + int i_OffsetRange; + int i_Coupling; + int i_SingleDiff; + int i_AutoCalibration; + unsigned int ui_ReloadValue; + unsigned int ui_TimeUnitReloadVal; + int i_Interrupt; + int i_ModuleSelection; +} Config_Parameters_Module1, Config_Parameters_Module2, + Config_Parameters_Module3, Config_Parameters_Module4; + + +struct str_ADDIDATA_RTDStruct { + unsigned int ul_NumberOfValue; + unsigned int *pul_ResistanceValue; + unsigned int *pul_TemperatureValue; +}; + +struct str_Module { + unsigned long ul_CurrentSourceCJC; + unsigned long ul_CurrentSource[5]; + unsigned long ul_GainFactor[8]; /* Gain Factor */ + unsigned int w_GainValue[10]; +}; + +struct str_BoardInfos { + + int i_CJCAvailable; + int i_CJCPolarity; + int i_CJCGain; + int i_InterruptFlag; + int i_ADDIDATAPolarity; + int i_ADDIDATAGain; + int i_AutoCalibration; + int i_ADDIDATAConversionTime; + int i_ADDIDATAConversionTimeUnit; + int i_ADDIDATAType; + int i_ChannelNo; + int i_ChannelCount; + int i_ScanType; + int i_FirstChannel; + int i_LastChannel; + int i_Sum; + int i_Offset; + unsigned int ui_Channel_num; + int i_Count; + int i_Initialised; + unsigned int ui_InterruptChannelValue[144]; /* Buffer */ + unsigned char b_StructInitialized; + /* 7 is the maximal number of channels */ + unsigned int ui_ScanValueArray[7 + 12]; + + int i_ConnectionType; + int i_NbrOfModule; + struct str_Module s_Module[MAX_MODULE]; +}; + +/* BEGIN JK 06.07.04: Management of sevrals boards */ +/* + int i_CJCAvailable=1; + int i_CJCPolarity=0; + int i_CJCGain=2;/* changed from 0 to 2 */ + int i_InterruptFlag=0; + int i_ADDIDATAPolarity; + int i_ADDIDATAGain; + int i_AutoCalibration=0; /* : auto calibration */ + int i_ADDIDATAConversionTime; + int i_ADDIDATAConversionTimeUnit; + int i_ADDIDATAType; + int i_ChannelNo; + int i_ChannelCount=0; + int i_ScanType; + int i_FirstChannel; + int i_LastChannel; + int i_Sum=0; + int i_Offset; + unsigned int ui_Channel_num=0; + static int i_Count=0; + int i_Initialised=0; + unsigned int ui_InterruptChannelValue[96]; /* Buffer */ +*/ +struct str_BoardInfos s_BoardInfos[100]; /* 100 will be the max number of boards to be used */ +/* END JK 06.07.04: Management of sevrals boards */ + +#define AMCC_OP_REG_MCSR 0x3c +#define EEPROM_BUSY 0x80000000 +#define NVCMD_LOAD_LOW (0x4 << 5) /* nvRam load low command */ +#define NVCMD_LOAD_HIGH (0x5 << 5) /* nvRam load high command */ +#define NVCMD_BEGIN_READ (0x7 << 5) /* nvRam begin read command */ +#define NVCMD_BEGIN_WRITE (0x6 << 5) /* EEPROM begin write command */ + +static int i_AddiHeaderRW_ReadEeprom(int i_NbOfWordsToRead, + unsigned int dw_PCIBoardEepromAddress, + unsigned short w_EepromStartAddress, + unsigned short *pw_DataRead) +{ + unsigned int dw_eeprom_busy = 0; + int i_Counter = 0; + int i_WordCounter; + int i; + unsigned char pb_ReadByte[1]; + unsigned char b_ReadLowByte = 0; + unsigned char b_ReadHighByte = 0; + unsigned char b_SelectedAddressLow = 0; + unsigned char b_SelectedAddressHigh = 0; + unsigned short w_ReadWord = 0; + + for (i_WordCounter = 0; i_WordCounter < i_NbOfWordsToRead; + i_WordCounter++) { + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + for (i_Counter = 0; i_Counter < 2; i_Counter++) { + b_SelectedAddressLow = (w_EepromStartAddress + i_Counter) % 256; /* Read the low 8 bit part */ + b_SelectedAddressHigh = (w_EepromStartAddress + i_Counter) / 256; /* Read the high 8 bit part */ + + /* Select the load low address mode */ + outb(NVCMD_LOAD_LOW, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Load the low address */ + outb(b_SelectedAddressLow, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the load high address mode */ + outb(NVCMD_LOAD_HIGH, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Load the high address */ + outb(b_SelectedAddressHigh, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the READ mode */ + outb(NVCMD_BEGIN_READ, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Read data into the EEPROM */ + *pb_ReadByte = + inb(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the upper address part */ + if (i_Counter == 0) + b_ReadLowByte = pb_ReadByte[0]; + else + b_ReadHighByte = pb_ReadByte[0]; + + + /* Sleep */ + msleep(1); + + } + w_ReadWord = + (b_ReadLowByte | (((unsigned short)b_ReadHighByte) * + 256)); + + pw_DataRead[i_WordCounter] = w_ReadWord; + + w_EepromStartAddress += 2; /* to read the next word */ + + } /* for (...) i_NbOfWordsToRead */ + return 0; +} + +static void v_GetAPCI3200EepromCalibrationValue(unsigned int dw_PCIBoardEepromAddress, + struct str_BoardInfos *BoardInformations) +{ + unsigned short w_AnalogInputMainHeaderAddress; + unsigned short w_AnalogInputComponentAddress; + unsigned short w_NumberOfModuls = 0; + unsigned short w_CurrentSources[2]; + unsigned short w_ModulCounter = 0; + unsigned short w_FirstHeaderSize = 0; + unsigned short w_NumberOfInputs = 0; + unsigned short w_CJCFlag = 0; + unsigned short w_NumberOfGainValue = 0; + unsigned short w_SingleHeaderAddress = 0; + unsigned short w_SingleHeaderSize = 0; + unsigned short w_Input = 0; + unsigned short w_GainFactorAddress = 0; + unsigned short w_GainFactorValue[2]; + unsigned short w_GainIndex = 0; + unsigned short w_GainValue = 0; + + /*****************************************/ + /** Get the Analog input header address **/ + /*****************************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, 0x116, /* w_EepromStartAddress: Analog input header address */ + &w_AnalogInputMainHeaderAddress); + + /*******************************************/ + /** Compute the real analog input address **/ + /*******************************************/ + w_AnalogInputMainHeaderAddress = w_AnalogInputMainHeaderAddress + 0x100; + + /******************************/ + /** Get the number of moduls **/ + /******************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputMainHeaderAddress + 0x02, /* w_EepromStartAddress: Number of conponment */ + &w_NumberOfModuls); + + for (w_ModulCounter = 0; w_ModulCounter < w_NumberOfModuls; + w_ModulCounter++) { + /***********************************/ + /** Compute the component address **/ + /***********************************/ + w_AnalogInputComponentAddress = + w_AnalogInputMainHeaderAddress + + (w_FirstHeaderSize * w_ModulCounter) + 0x04; + + /****************************/ + /** Read first header size **/ + /****************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress, /* Address of the first header */ + &w_FirstHeaderSize); + + w_FirstHeaderSize = w_FirstHeaderSize >> 4; + + /***************************/ + /** Read number of inputs **/ + /***************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 0x06, /* Number of inputs for the first modul */ + &w_NumberOfInputs); + + w_NumberOfInputs = w_NumberOfInputs >> 4; + + /***********************/ + /** Read the CJC flag **/ + /***********************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 0x08, /* CJC flag */ + &w_CJCFlag); + + w_CJCFlag = (w_CJCFlag >> 3) & 0x1; /* Get only the CJC flag */ + + /*******************************/ + /** Read number of gain value **/ + /*******************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 0x44, /* Number of gain value */ + &w_NumberOfGainValue); + + w_NumberOfGainValue = w_NumberOfGainValue & 0xFF; + + /***********************************/ + /** Compute single header address **/ + /***********************************/ + w_SingleHeaderAddress = + w_AnalogInputComponentAddress + 0x46 + + (((w_NumberOfGainValue / 16) + 1) * 2) + + (6 * w_NumberOfGainValue) + + (4 * (((w_NumberOfGainValue / 16) + 1) * 2)); + + /********************************************/ + /** Read current sources value for input 1 **/ + /********************************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_SingleHeaderAddress, /* w_EepromStartAddress: Single header address */ + &w_SingleHeaderSize); + + w_SingleHeaderSize = w_SingleHeaderSize >> 4; + + /*************************************/ + /** Read gain factor for the module **/ + /*************************************/ + w_GainFactorAddress = w_AnalogInputComponentAddress; + + for (w_GainIndex = 0; w_GainIndex < w_NumberOfGainValue; + w_GainIndex++) { + /************************************/ + /** Read gain value for the module **/ + /************************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 70 + (2 * (1 + (w_NumberOfGainValue / 16))) + (0x02 * w_GainIndex), /* Gain value */ + &w_GainValue); + + BoardInformations->s_Module[w_ModulCounter]. + w_GainValue[w_GainIndex] = w_GainValue; + +# ifdef PRINT_INFO + printk("\n Gain value = %d", + BoardInformations->s_Module[w_ModulCounter]. + w_GainValue[w_GainIndex]); +# endif + + /*************************************/ + /** Read gain factor for the module **/ + /*************************************/ + i_AddiHeaderRW_ReadEeprom(2, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 70 + ((2 * w_NumberOfGainValue) + (2 * (1 + (w_NumberOfGainValue / 16)))) + (0x04 * w_GainIndex), /* Gain factor */ + w_GainFactorValue); + + BoardInformations->s_Module[w_ModulCounter]. + ul_GainFactor[w_GainIndex] = + (w_GainFactorValue[1] << 16) + + w_GainFactorValue[0]; + +# ifdef PRINT_INFO + printk("\n w_GainFactorValue [%d] = %lu", w_GainIndex, + BoardInformations->s_Module[w_ModulCounter]. + ul_GainFactor[w_GainIndex]); +# endif + } + + /***************************************************************/ + /** Read current source value for each channels of the module **/ + /***************************************************************/ + for (w_Input = 0; w_Input < w_NumberOfInputs; w_Input++) { + /********************************************/ + /** Read current sources value for input 1 **/ + /********************************************/ + i_AddiHeaderRW_ReadEeprom(2, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, + (w_Input * w_SingleHeaderSize) + + w_SingleHeaderAddress + 0x0C, w_CurrentSources); + + /************************************/ + /** Save the current sources value **/ + /************************************/ + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSource[w_Input] = + (w_CurrentSources[0] + + ((w_CurrentSources[1] & 0xFFF) << 16)); + +# ifdef PRINT_INFO + printk("\n Current sources [%d] = %lu", w_Input, + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSource[w_Input]); +# endif + } + + /***************************************/ + /** Read the CJC current source value **/ + /***************************************/ + i_AddiHeaderRW_ReadEeprom(2, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, + (w_Input * w_SingleHeaderSize) + w_SingleHeaderAddress + + 0x0C, w_CurrentSources); + + /************************************/ + /** Save the current sources value **/ + /************************************/ + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSourceCJC = + (w_CurrentSources[0] + + ((w_CurrentSources[1] & 0xFFF) << 16)); + +# ifdef PRINT_INFO + printk("\n Current sources CJC = %lu", + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSourceCJC); +# endif + } +} + +static int i_APCI3200_GetChannelCalibrationValue(struct comedi_device *dev, + unsigned int ui_Channel_num, + unsigned int *CJCCurrentSource, + unsigned int *ChannelCurrentSource, + unsigned int *ChannelGainFactor) +{ + int i_DiffChannel = 0; + int i_Module = 0; + +#ifdef PRINT_INFO + printk("\n Channel = %u", ui_Channel_num); +#endif + + /* Test if single or differential mode */ + if (s_BoardInfos[dev->minor].i_ConnectionType == 1) { + /* if diff */ + + if (ui_Channel_num <= 1) + i_DiffChannel = ui_Channel_num, i_Module = 0; + else if ((ui_Channel_num >= 2) && (ui_Channel_num <= 3)) + i_DiffChannel = ui_Channel_num - 2, i_Module = 1; + else if ((ui_Channel_num >= 4) && (ui_Channel_num <= 5)) + i_DiffChannel = ui_Channel_num - 4, i_Module = 2; + else if ((ui_Channel_num >= 6) && (ui_Channel_num <= 7)) + i_DiffChannel = ui_Channel_num - 6, i_Module = 3; + + } else { + /* if single */ + if ((ui_Channel_num == 0) || (ui_Channel_num == 1)) + i_DiffChannel = 0, i_Module = 0; + else if ((ui_Channel_num == 2) || (ui_Channel_num == 3)) + i_DiffChannel = 1, i_Module = 0; + else if ((ui_Channel_num == 4) || (ui_Channel_num == 5)) + i_DiffChannel = 0, i_Module = 1; + else if ((ui_Channel_num == 6) || (ui_Channel_num == 7)) + i_DiffChannel = 1, i_Module = 1; + else if ((ui_Channel_num == 8) || (ui_Channel_num == 9)) + i_DiffChannel = 0, i_Module = 2; + else if ((ui_Channel_num == 10) || (ui_Channel_num == 11)) + i_DiffChannel = 1, i_Module = 2; + else if ((ui_Channel_num == 12) || (ui_Channel_num == 13)) + i_DiffChannel = 0, i_Module = 3; + else if ((ui_Channel_num == 14) || (ui_Channel_num == 15)) + i_DiffChannel = 1, i_Module = 3; + } + + /* Test if thermocouple or RTD mode */ + *CJCCurrentSource = + s_BoardInfos[dev->minor].s_Module[i_Module].ul_CurrentSourceCJC; +#ifdef PRINT_INFO + printk("\n CJCCurrentSource = %lu", *CJCCurrentSource); +#endif + + *ChannelCurrentSource = + s_BoardInfos[dev->minor].s_Module[i_Module]. + ul_CurrentSource[i_DiffChannel]; +#ifdef PRINT_INFO + printk("\n ChannelCurrentSource = %lu", *ChannelCurrentSource); +#endif + /* } */ + /* } */ + + /* Channle gain factor */ + *ChannelGainFactor = + s_BoardInfos[dev->minor].s_Module[i_Module]. + ul_GainFactor[s_BoardInfos[dev->minor].i_ADDIDATAGain]; +#ifdef PRINT_INFO + printk("\n ChannelGainFactor = %lu", *ChannelGainFactor); +#endif + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + return 0; +} + +static int apci3200_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + + data[1] = inl(devpriv->i_IobaseReserved) & 0xf; + + return insn->n; +} + +static int apci3200_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + + s->state = inl(devpriv->i_IobaseAddon) & 0xf; + + if (comedi_dio_update_state(s, data)) + outl(s->state, devpriv->i_IobaseAddon); + + data[1] = s->state; + + return insn->n; +} + +static int i_APCI3200_Read1AnalogInputChannel(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_EOC = 0; + unsigned int ui_ChannelNo = 0; + unsigned int ui_CommandRegister = 0; + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_ChannelNo=i_ChannelNo; */ + ui_ChannelNo = s_BoardInfos[dev->minor].i_ChannelNo; + + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* Begin JK 20.10.2004: Bad channel value is used when using differential mode */ + /* outl(0 | ui_Channel_num , devpriv->iobase+i_Offset + 0x4); */ + /* outl(0 | s_BoardInfos [dev->minor].ui_Channel_num , devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 0x4); */ + outl(0 | s_BoardInfos[dev->minor].i_ChannelNo, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x4); + /* End JK 20.10.2004: Bad channel value is used when using differential mode */ + + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + + /**************************************************************************/ + /* Set the start end stop index to the selected channel and set the start */ + /**************************************************************************/ + + ui_CommandRegister = ui_ChannelNo | (ui_ChannelNo << 8) | 0x80000; + + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /************************/ + /* Enable the interrupt */ + /************************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + } /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + + /******************************/ + /* Write the command register */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(ui_CommandRegister, devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + /*************************/ + /*Read the EOC Status bit */ + /*************************/ + + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /***************************************/ + /* Read the digital value of the input */ + /***************************************/ + + /* data[0] = inl (devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + /* END JK 06.07.04: Management of sevrals boards */ + + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +static int i_APCI3200_ReadCalibrationOffsetValue(struct comedi_device *dev, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Temp = 0, ui_EOC = 0; + unsigned int ui_CommandRegister = 0; + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* Begin JK 20.10.2004: This seems not necessary ! */ + /* outl(0 | ui_Channel_num , devpriv->iobase+i_Offset + 0x4); */ + /* outl(0 | s_BoardInfos [dev->minor].ui_Channel_num , devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 0x4); */ + /* End JK 20.10.2004: This seems not necessary ! */ + + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /*****************************/ + /*Read the calibration offset */ + /*****************************/ + /* ui_Temp = inl(devpriv->iobase+i_Offset + 12); */ + ui_Temp = inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + + /*********************************/ + /*Configure the Offset Conversion */ + /*********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Temp | 0x00020000), devpriv->iobase+i_Offset + 12); */ + outl((ui_Temp | 0x00020000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /*******************************/ + /*Initialise ui_CommandRegister */ + /*******************************/ + + ui_CommandRegister = 0; + + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + + /**********************/ + /*Enable the interrupt */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00100000; + + } /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + + /**********************/ + /*Start the conversion */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00080000; + + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister, devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + + do { + /*******************/ + /*Read the EOC flag */ + /*******************/ + + /* ui_EOC = inl (devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /**************************************************/ + /*Read the digital value of the calibration Offset */ + /**************************************************/ + + /* data[0] = inl(devpriv->iobase+i_Offset+ 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +static int i_APCI3200_ReadCalibrationGainValue(struct comedi_device *dev, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* Begin JK 20.10.2004: This seems not necessary ! */ + /* outl(0 | ui_Channel_num , devpriv->iobase+i_Offset + 0x4); */ + /* outl(0 | s_BoardInfos [dev->minor].ui_Channel_num , devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 0x4); */ + /* End JK 20.10.2004: This seems not necessary ! */ + + /***************************/ + /*Read the calibration gain */ + /***************************/ + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /*******************************/ + /*Configure the Gain Conversion */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00040000 , devpriv->iobase+i_Offset + 12); */ + outl(0x00040000, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + + /*******************************/ + /*Initialise ui_CommandRegister */ + /*******************************/ + + ui_CommandRegister = 0; + + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + + /**********************/ + /*Enable the interrupt */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00100000; + + } /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + + /**********************/ + /*Start the conversion */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00080000; + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister , devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + + do { + + /*******************/ + /*Read the EOC flag */ + /*******************/ + + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /************************************************/ + /*Read the digital value of the calibration Gain */ + /************************************************/ + + /* data[0] = inl(devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +static int i_APCI3200_ReadCJCValue(struct comedi_device *dev, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + + /******************************/ + /*Set the converting time unit */ + /******************************/ + + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + + /******************************/ + /*Configure the CJC Conversion */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl( 0x00000400 , devpriv->iobase+i_Offset + 4); */ + outl(0x00000400, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*******************************/ + /*Initialise dw_CommandRegister */ + /*******************************/ + ui_CommandRegister = 0; + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /**********************/ + /*Enable the interrupt */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + } + + /**********************/ + /*Start the conversion */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00080000; + + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister , devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + + /*******************/ + /*Read the EOC flag */ + /*******************/ + + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /***********************************/ + /*Read the digital value of the CJC */ + /***********************************/ + + /* data[0] = inl(devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +static int i_APCI3200_ReadCJCCalOffset(struct comedi_device *dev, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + + /*******************************************/ + /*Read calibration offset value for the CJC */ + /*******************************************/ + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /******************************/ + /*Configure the CJC Conversion */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00000400 , devpriv->iobase+i_Offset + 4); */ + outl(0x00000400, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*********************************/ + /*Configure the Offset Conversion */ + /*********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00020000, devpriv->iobase+i_Offset + 12); */ + outl(0x00020000, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /*******************************/ + /*Initialise ui_CommandRegister */ + /*******************************/ + ui_CommandRegister = 0; + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /**********************/ + /*Enable the interrupt */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + + } + + /**********************/ + /*Start the conversion */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00080000; + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister,devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + /*******************/ + /*Read the EOC flag */ + /*******************/ + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + } while (ui_EOC != 1); + + /**************************************************/ + /*Read the digital value of the calibration Offset */ + /**************************************************/ + /* data[0] = inl(devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +static int i_APCI3200_ReadCJCCalGain(struct comedi_device *dev, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /******************************/ + /*Configure the CJC Conversion */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00000400,devpriv->iobase+i_Offset + 4); */ + outl(0x00000400, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*******************************/ + /*Configure the Gain Conversion */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00040000,devpriv->iobase+i_Offset + 12); */ + outl(0x00040000, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + + /*******************************/ + /*Initialise dw_CommandRegister */ + /*******************************/ + ui_CommandRegister = 0; + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /**********************/ + /*Enable the interrupt */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + } + /**********************/ + /*Start the conversion */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00080000; + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister ,devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + /*******************/ + /*Read the EOC flag */ + /*******************/ + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + } while (ui_EOC != 1); + /************************************************/ + /*Read the digital value of the calibration Gain */ + /************************************************/ + /* data[0] = inl (devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +static int apci3200_reset(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + int i_Temp; + unsigned int dw_Dummy; + + /* i_InterruptFlag=0; */ + /* i_Initialised==0; */ + /* i_Count=0; */ + /* i_Sum=0; */ + + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + s_BoardInfos[dev->minor].b_StructInitialized = 0; + + outl(0x83838383, devpriv->i_IobaseAmcc + 0x60); + + /* Enable the interrupt for the controller */ + dw_Dummy = inl(devpriv->i_IobaseAmcc + 0x38); + outl(dw_Dummy | 0x2000, devpriv->i_IobaseAmcc + 0x38); + outl(0, devpriv->i_IobaseAddon); /* Resets the output */ + /***************/ + /*Empty the buffer */ + /**************/ + for (i_Temp = 0; i_Temp <= 95; i_Temp++) { + /* ui_InterruptChannelValue[i_Temp]=0; */ + s_BoardInfos[dev->minor].ui_InterruptChannelValue[i_Temp] = 0; + } /* for(i_Temp=0;i_Temp<=95;i_Temp++) */ + /*****************************/ + /*Reset the START and IRQ bit */ + /*****************************/ + for (i_Temp = 0; i_Temp <= 192;) { + while (((inl(devpriv->iobase + i_Temp + 12) >> 19) & 1) != 1) ; + outl(0, devpriv->iobase + i_Temp + 8); + i_Temp = i_Temp + 64; + } /* for(i_Temp=0;i_Temp<=192;i_Temp+64) */ + return 0; +} + +/* + * Read value of the selected channel + * + * data[0] : Digital Value Of Input + * data[1] : Calibration Offset Value + * data[2] : Calibration Gain Value + * data[3] : CJC value + * data[4] : CJC offset value + * data[5] : CJC gain value + * data[6] : CJC current source from eeprom + * data[7] : Channel current source from eeprom + * data[8] : Channle gain factor from eeprom + */ +static int apci3200_ai_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int ui_DummyValue = 0; + int i_ConvertCJCCalibration; + int i = 0; + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_Initialised==0) */ + if (s_BoardInfos[dev->minor].i_Initialised == 0) + /* END JK 06.07.04: Management of sevrals boards */ + { + apci3200_reset(dev); + return -EINVAL; + } /* if(i_Initialised==0); */ + +#ifdef PRINT_INFO + printk("\n insn->unused[0] = %i", insn->unused[0]); +#endif + + switch (insn->unused[0]) { + case 0: + + i_APCI3200_Read1AnalogInputChannel(dev, s, insn, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+0]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 0] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + + /* Begin JK 25.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + i_APCI3200_GetChannelCalibrationValue(dev, + s_BoardInfos[dev->minor].ui_Channel_num, + &s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 6], + &s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 7], + &s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 8]); + +#ifdef PRINT_INFO + printk("\n s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count+6] = %lu", s_BoardInfos[dev->minor].ui_InterruptChannelValue[s_BoardInfos[dev->minor].i_Count + 6]); + + printk("\n s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count+7] = %lu", s_BoardInfos[dev->minor].ui_InterruptChannelValue[s_BoardInfos[dev->minor].i_Count + 7]); + + printk("\n s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count+8] = %lu", s_BoardInfos[dev->minor].ui_InterruptChannelValue[s_BoardInfos[dev->minor].i_Count + 8]); +#endif + + /* End JK 25.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE) && (i_CJCAvailable==1)) */ + if ((s_BoardInfos[dev->minor].i_ADDIDATAType == 2) + && (s_BoardInfos[dev->minor].i_InterruptFlag == FALSE) + && (s_BoardInfos[dev->minor].i_CJCAvailable == 1)) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_APCI3200_ReadCJCValue(dev, &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 3]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 3] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE)) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 3]=0; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 3] = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* elseif((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE) && (i_CJCAvailable==1)) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if (( i_AutoCalibration == FALSE) && (i_InterruptFlag == FALSE)) */ + if ((s_BoardInfos[dev->minor].i_AutoCalibration == FALSE) + && (s_BoardInfos[dev->minor].i_InterruptFlag == FALSE)) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_APCI3200_ReadCalibrationOffsetValue(dev, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 1]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 1] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + i_APCI3200_ReadCalibrationGainValue(dev, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 2]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 2] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if (( i_AutoCalibration == FALSE) && (i_InterruptFlag == FALSE)) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE)&& (i_CJCAvailable==1)) */ + if ((s_BoardInfos[dev->minor].i_ADDIDATAType == 2) + && (s_BoardInfos[dev->minor].i_InterruptFlag == FALSE) + && (s_BoardInfos[dev->minor].i_CJCAvailable == 1)) + /* END JK 06.07.04: Management of sevrals boards */ + { + /**********************************************************/ + /*Test if the Calibration channel must be read for the CJC */ + /**********************************************************/ + /**********************************/ + /*Test if the polarity is the same */ + /**********************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (s_BoardInfos[dev->minor].i_CJCPolarity != + s_BoardInfos[dev->minor].i_ADDIDATAPolarity) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_ConvertCJCCalibration = 1; + } /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_CJCGain==i_ADDIDATAGain) */ + if (s_BoardInfos[dev->minor].i_CJCGain == + s_BoardInfos[dev->minor].i_ADDIDATAGain) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_ConvertCJCCalibration = 0; + } /* if(i_CJCGain==i_ADDIDATAGain) */ + else { + i_ConvertCJCCalibration = 1; + } /* elseif(i_CJCGain==i_ADDIDATAGain) */ + } /* elseif(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (i_ConvertCJCCalibration == 1) { + i_APCI3200_ReadCJCCalOffset(dev, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+4]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 4] = + ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + + i_APCI3200_ReadCJCCalGain(dev, &ui_DummyValue); + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+5]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 5] = + ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(i_ConvertCJCCalibration==1) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+4]=0; */ + /* ui_InterruptChannelValue[i_Count+5]=0; */ + + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 4] = 0; + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 5] = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* elseif(i_ConvertCJCCalibration==1) */ + } /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE)) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor].i_ScanType != 1) { + /* i_Count=0; */ + s_BoardInfos[dev->minor].i_Count = 0; + } /* if(i_ScanType!=1) */ + else { + /* i_Count=i_Count +6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count +6; */ + s_BoardInfos[dev->minor].i_Count = + s_BoardInfos[dev->minor].i_Count + 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } /* else if(i_ScanType!=1) */ + + /* if((i_ScanType==1) &&(i_InterruptFlag==1)) */ + if ((s_BoardInfos[dev->minor].i_ScanType == 1) + && (s_BoardInfos[dev->minor].i_InterruptFlag == 1)) { + /* i_Count=i_Count-6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count-6; */ + s_BoardInfos[dev->minor].i_Count = + s_BoardInfos[dev->minor].i_Count - 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } + /* if(i_ScanType==0) */ + if (s_BoardInfos[dev->minor].i_ScanType == 0) { + /* + data[0]= ui_InterruptChannelValue[0]; + data[1]= ui_InterruptChannelValue[1]; + data[2]= ui_InterruptChannelValue[2]; + data[3]= ui_InterruptChannelValue[3]; + data[4]= ui_InterruptChannelValue[4]; + data[5]= ui_InterruptChannelValue[5]; + */ +#ifdef PRINT_INFO + printk("\n data[0]= s_BoardInfos [dev->minor].ui_InterruptChannelValue[0];"); +#endif + data[0] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[0]; + data[1] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[1]; + data[2] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[2]; + data[3] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[3]; + data[4] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[4]; + data[5] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[5]; + + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* printk("\n 0 - i_APCI3200_GetChannelCalibrationValue data [6] = %lu, data [7] = %lu, data [8] = %lu", data [6], data [7], data [8]); */ + i_APCI3200_GetChannelCalibrationValue(dev, + s_BoardInfos[dev->minor].ui_Channel_num, + &data[6], &data[7], &data[8]); + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } + break; + case 1: + + for (i = 0; i < insn->n; i++) { + /* data[i]=ui_InterruptChannelValue[i]; */ + data[i] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[i]; + } + + /* i_Count=0; */ + /* i_Sum=0; */ + /* if(i_ScanType==1) */ + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + if (s_BoardInfos[dev->minor].i_ScanType == 1) { + /* i_Initialised=0; */ + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } + break; + default: + printk("\nThe parameters passed are in error\n"); + apci3200_reset(dev); + return -EINVAL; + } /* switch(insn->unused[0]) */ + + return insn->n; +} + +/* + * Configures The Analog Input Subdevice + * + * data[0] = 0 Normal AI + * = 1 RTD + * = 2 THERMOCOUPLE + * data[1] = Gain To Use + * data[2] = 0 Bipolar + * = 1 Unipolar + * data[3] = Offset Range + * data[4] = 0 DC Coupling + * = 1 AC Coupling + * data[5] = 0 Single + * = 1 Differential + * data[6] = TimerReloadValue + * data[7] = ConvertingTimeUnit + * data[8] = 0 Analog voltage measurement + * = 1 Resistance measurement + * = 2 Temperature measurement + * data[9] = 0 Interrupt Disable + * = 1 INterrupt Enable + * data[10] = Type of Thermocouple + * data[11] = single channel Module Number + * data[12] = 0 Single Read + * = 1 Read more channel + * = 2 Single scan + * = 3 Continuous Scan + * data[13] = Number of channels to read + * data[14] = 0 RTD not used + * = 1 RTD 2 wire connection + * = 2 RTD 3 wire connection + * = 3 RTD 4 wire connection + */ +static int apci3200_ai_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ul_Config = 0, ul_Temp = 0; + unsigned int ui_ChannelNo = 0; + unsigned int ui_Dummy = 0; + int i_err = 0; + + /* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + +#ifdef PRINT_INFO + int i = 0, i2 = 0; +#endif + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* Initialize the structure */ + if (s_BoardInfos[dev->minor].b_StructInitialized != 1) { + s_BoardInfos[dev->minor].i_CJCAvailable = 1; + s_BoardInfos[dev->minor].i_CJCPolarity = 0; + s_BoardInfos[dev->minor].i_CJCGain = 2; /* changed from 0 to 2 */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + s_BoardInfos[dev->minor].i_AutoCalibration = 0; /* : auto calibration */ + s_BoardInfos[dev->minor].i_ChannelCount = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + s_BoardInfos[dev->minor].ui_Channel_num = 0; + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].b_StructInitialized = 1; + + /* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + s_BoardInfos[dev->minor].i_ConnectionType = 0; + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + memset(s_BoardInfos[dev->minor].s_Module, 0, + sizeof(s_BoardInfos[dev->minor].s_Module[MAX_MODULE])); + + v_GetAPCI3200EepromCalibrationValue(devpriv->i_IobaseAmcc, + &s_BoardInfos[dev->minor]); + +#ifdef PRINT_INFO + for (i = 0; i < MAX_MODULE; i++) { + printk("\n s_Module[%i].ul_CurrentSourceCJC = %lu", i, + s_BoardInfos[dev->minor].s_Module[i]. + ul_CurrentSourceCJC); + + for (i2 = 0; i2 < 5; i2++) { + printk("\n s_Module[%i].ul_CurrentSource [%i] = %lu", i, i2, s_BoardInfos[dev->minor].s_Module[i].ul_CurrentSource[i2]); + } + + for (i2 = 0; i2 < 8; i2++) { + printk("\n s_Module[%i].ul_GainFactor [%i] = %lu", i, i2, s_BoardInfos[dev->minor].s_Module[i].ul_GainFactor[i2]); + } + + for (i2 = 0; i2 < 8; i2++) { + printk("\n s_Module[%i].w_GainValue [%i] = %u", + i, i2, + s_BoardInfos[dev->minor].s_Module[i]. + w_GainValue[i2]); + } + } +#endif + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } + + if (data[0] != 0 && data[0] != 1 && data[0] != 2) { + printk("\nThe selection of acquisition type is in error\n"); + i_err++; + } /* if(data[0]!=0 && data[0]!=1 && data[0]!=2) */ + if (data[0] == 1) { + if (data[14] != 0 && data[14] != 1 && data[14] != 2 + && data[14] != 4) { + printk("\n Error in selection of RTD connection type\n"); + i_err++; + } /* if(data[14]!=0 && data[14]!=1 && data[14]!=2 && data[14]!=4) */ + } /* if(data[0]==1 ) */ + if (data[1] < 0 || data[1] > 7) { + printk("\nThe selection of gain is in error\n"); + i_err++; + } /* if(data[1]<0 || data[1]>7) */ + if (data[2] != 0 && data[2] != 1) { + printk("\nThe selection of polarity is in error\n"); + i_err++; + } /* if(data[2]!=0 && data[2]!=1) */ + if (data[3] != 0) { + printk("\nThe selection of offset range is in error\n"); + i_err++; + } /* if(data[3]!=0) */ + if (data[4] != 0 && data[4] != 1) { + printk("\nThe selection of coupling is in error\n"); + i_err++; + } /* if(data[4]!=0 && data[4]!=1) */ + if (data[5] != 0 && data[5] != 1) { + printk("\nThe selection of single/differential mode is in error\n"); + i_err++; + } /* if(data[5]!=0 && data[5]!=1) */ + if (data[8] != 0 && data[8] != 1 && data[2] != 2) { + printk("\nError in selection of functionality\n"); + } /* if(data[8]!=0 && data[8]!=1 && data[2]!=2) */ + if (data[12] == 0 || data[12] == 1) { + if (data[6] != 20 && data[6] != 40 && data[6] != 80 + && data[6] != 160) { + printk("\nThe selection of conversion time reload value is in error\n"); + i_err++; + } /* if (data[6]!=20 && data[6]!=40 && data[6]!=80 && data[6]!=160 ) */ + if (data[7] != 2) { + printk("\nThe selection of conversion time unit is in error\n"); + i_err++; + } /* if(data[7]!=2) */ + } + if (data[9] != 0 && data[9] != 1) { + printk("\nThe selection of interrupt enable is in error\n"); + i_err++; + } /* if(data[9]!=0 && data[9]!=1) */ + if (data[11] < 0 || data[11] > 4) { + printk("\nThe selection of module is in error\n"); + i_err++; + } /* if(data[11] <0 || data[11]>1) */ + if (data[12] < 0 || data[12] > 3) { + printk("\nThe selection of singlechannel/scan selection is in error\n"); + i_err++; + } /* if(data[12] < 0 || data[12]> 3) */ + if (data[13] < 0 || data[13] > 16) { + printk("\nThe selection of number of channels is in error\n"); + i_err++; + } /* if(data[13] <0 ||data[13] >15) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* + i_ChannelCount=data[13]; + i_ScanType=data[12]; + i_ADDIDATAPolarity = data[2]; + i_ADDIDATAGain=data[1]; + i_ADDIDATAConversionTime=data[6]; + i_ADDIDATAConversionTimeUnit=data[7]; + i_ADDIDATAType=data[0]; + */ + + /* Save acquisition configuration for the actual board */ + s_BoardInfos[dev->minor].i_ChannelCount = data[13]; + s_BoardInfos[dev->minor].i_ScanType = data[12]; + s_BoardInfos[dev->minor].i_ADDIDATAPolarity = data[2]; + s_BoardInfos[dev->minor].i_ADDIDATAGain = data[1]; + s_BoardInfos[dev->minor].i_ADDIDATAConversionTime = data[6]; + s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit = data[7]; + s_BoardInfos[dev->minor].i_ADDIDATAType = data[0]; + /* Begin JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + s_BoardInfos[dev->minor].i_ConnectionType = data[5]; + /* End JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* END JK 06.07.04: Management of sevrals boards */ + + /* Begin JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + memset(s_BoardInfos[dev->minor].ui_ScanValueArray, 0, (7 + 12) * sizeof(unsigned int)); /* 7 is the maximal number of channels */ + /* End JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /* BEGIN JK 02.07.04 : This while can't be do, it block the process when using severals boards */ + /* while(i_InterruptFlag==1) */ + while (s_BoardInfos[dev->minor].i_InterruptFlag == 1) { +#ifndef MSXBOX + udelay(1); +#else + /* In the case where the driver is compiled for the MSX-Box */ + /* we used a printk to have a little delay because udelay */ + /* seems to be broken under the MSX-Box. */ + /* This solution hat to be studied. */ + printk(""); +#endif + } + /* END JK 02.07.04 : This while can't be do, it block the process when using severals boards */ + + ui_ChannelNo = CR_CHAN(insn->chanspec); /* get the channel */ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=ui_ChannelNo; */ + /* ui_Channel_num =ui_ChannelNo; */ + + s_BoardInfos[dev->minor].i_ChannelNo = ui_ChannelNo; + s_BoardInfos[dev->minor].ui_Channel_num = ui_ChannelNo; + + /* END JK 06.07.04: Management of sevrals boards */ + + if (data[5] == 0) { + if (ui_ChannelNo < 0 || ui_ChannelNo > 15) { + printk("\nThe Selection of the channel is in error\n"); + i_err++; + } /* if(ui_ChannelNo<0 || ui_ChannelNo>15) */ + } /* if(data[5]==0) */ + else { + if (data[14] == 2) { + if (ui_ChannelNo < 0 || ui_ChannelNo > 3) { + printk("\nThe Selection of the channel is in error\n"); + i_err++; + } /* if(ui_ChannelNo<0 || ui_ChannelNo>3) */ + } /* if(data[14]==2) */ + else { + if (ui_ChannelNo < 0 || ui_ChannelNo > 7) { + printk("\nThe Selection of the channel is in error\n"); + i_err++; + } /* if(ui_ChannelNo<0 || ui_ChannelNo>7) */ + } /* elseif(data[14]==2) */ + } /* elseif(data[5]==0) */ + if (data[12] == 0 || data[12] == 1) { + switch (data[5]) { + case 0: + if (ui_ChannelNo >= 0 && ui_ChannelNo <= 3) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=0 && ui_ChannelNo <=3) */ + if (ui_ChannelNo >= 4 && ui_ChannelNo <= 7) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=64; */ + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=4 && ui_ChannelNo <=7) */ + if (ui_ChannelNo >= 8 && ui_ChannelNo <= 11) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=8 && ui_ChannelNo <=11) */ + if (ui_ChannelNo >= 12 && ui_ChannelNo <= 15) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=12 && ui_ChannelNo <=15) */ + break; + case 1: + if (data[14] == 2) { + if (ui_ChannelNo == 0) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==0 ) */ + if (ui_ChannelNo == 1) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==1) */ + if (ui_ChannelNo == 2) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==2 ) */ + if (ui_ChannelNo == 3) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==3) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=0; */ + s_BoardInfos[dev->minor].i_ChannelNo = 0; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = 0; + break; + } /* if(data[14]==2) */ + if (ui_ChannelNo >= 0 && ui_ChannelNo <= 1) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=0 && ui_ChannelNo <=1) */ + if (ui_ChannelNo >= 2 && ui_ChannelNo <= 3) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=i_ChannelNo-2; */ + /* i_Offset=64; */ + s_BoardInfos[dev->minor].i_ChannelNo = + s_BoardInfos[dev->minor].i_ChannelNo - + 2; + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = ui_ChannelNo - 2; + } /* if(ui_ChannelNo >=2 && ui_ChannelNo <=3) */ + if (ui_ChannelNo >= 4 && ui_ChannelNo <= 5) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=i_ChannelNo-4; */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_ChannelNo = + s_BoardInfos[dev->minor].i_ChannelNo - + 4; + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = ui_ChannelNo - 4; + } /* if(ui_ChannelNo >=4 && ui_ChannelNo <=5) */ + if (ui_ChannelNo >= 6 && ui_ChannelNo <= 7) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=i_ChannelNo-6; */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_ChannelNo = + s_BoardInfos[dev->minor].i_ChannelNo - + 6; + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = ui_ChannelNo - 6; + } /* if(ui_ChannelNo >=6 && ui_ChannelNo <=7) */ + break; + + default: + printk("\n This selection of polarity does not exist\n"); + i_err++; + } /* switch(data[2]) */ + } /* if(data[12]==0 || data[12]==1) */ + else { + switch (data[11]) { + case 1: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + break; + case 2: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=64; */ + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + break; + case 3: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + break; + case 4: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + break; + default: + printk("\nError in module selection\n"); + i_err++; + } /* switch(data[11]) */ + } /* elseif(data[12]==0 || data[12]==1) */ + if (i_err) { + apci3200_reset(dev); + return -EINVAL; + } + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor].i_ScanType != 1) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Count=0; */ + /* i_Sum=0; */ + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(i_ScanType!=1) */ + + ul_Config = + data[1] | (data[2] << 6) | (data[5] << 7) | (data[3] << 8) | + (data[4] << 9); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl(0 | ui_ChannelNo , devpriv->iobase+i_Offset + 0x4); */ + outl(0 | ui_ChannelNo, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x4); + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + /**************************/ + /* Reset the configuration */ + /**************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl(0 , devpriv->iobase+i_Offset + 0x0); */ + outl(0, devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x0); + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + + /***************************/ + /* Write the configuration */ + /***************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl(ul_Config , devpriv->iobase+i_Offset + 0x0); */ + outl(ul_Config, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x0); + /* END JK 06.07.04: Management of sevrals boards */ + + /***************************/ + /*Reset the calibration bit */ + /***************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ul_Temp = inl(devpriv->iobase+i_Offset + 12); */ + ul_Temp = inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl((ul_Temp & 0xFFF9FFFF) , devpriv->iobase+.i_Offset + 12); */ + outl((ul_Temp & 0xFFF9FFFF), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /* END JK 06.07.04: Management of sevrals boards */ + + if (data[9] == 1) { + devpriv->tsk_Current = current; + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_InterruptFlag=1; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 1; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(data[9]==1) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* else if(data[9]==1) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Initialised=1; */ + s_BoardInfos[dev->minor].i_Initialised = 1; + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_ScanType==1) */ + if (s_BoardInfos[dev->minor].i_ScanType == 1) + /* END JK 06.07.04: Management of sevrals boards */ + { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Sum=i_Sum+1; */ + s_BoardInfos[dev->minor].i_Sum = + s_BoardInfos[dev->minor].i_Sum + 1; + /* END JK 06.07.04: Management of sevrals boards */ + + insn->unused[0] = 0; + apci3200_ai_read(dev, s, insn, &ui_Dummy); + } + + return insn->n; +} + +/* + * Tests the Selected Anlog Input Channel + * + * data[0] = 0 TestAnalogInputShortCircuit + * = 1 TestAnalogInputConnection + * + * data[0] : Digital value obtained + * data[1] : calibration offset + * data[2] : calibration gain + */ +static int apci3200_ai_bits_test(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Configuration = 0; + int i_Temp; /* ,i_TimeUnit; */ + + /* if(i_Initialised==0) */ + + if (s_BoardInfos[dev->minor].i_Initialised == 0) { + apci3200_reset(dev); + return -EINVAL; + } /* if(i_Initialised==0); */ + if (data[0] != 0 && data[0] != 1) { + printk("\nError in selection of functionality\n"); + apci3200_reset(dev); + return -EINVAL; + } /* if(data[0]!=0 && data[0]!=1) */ + + if (data[0] == 1) /* Perform Short Circuit TEST */ + { + /**************************/ + /*Set the short-cicuit bit */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + 12) >> 19) & 1) != + 1) ; + /* outl((0x00001000 |i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00001000 | s_BoardInfos[dev->minor].i_ChannelNo), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 4); + /*************************/ + /*Set the time unit to ns */ + /*************************/ + /* i_TimeUnit= i_ADDIDATAConversionTimeUnit; + i_ADDIDATAConversionTimeUnit= 1; */ + /* i_Temp= i_InterruptFlag ; */ + i_Temp = s_BoardInfos[dev->minor].i_InterruptFlag; + /* i_InterruptFlag = ADDIDATA_DISABLE; */ + s_BoardInfos[dev->minor].i_InterruptFlag = ADDIDATA_DISABLE; + i_APCI3200_Read1AnalogInputChannel(dev, s, insn, data); + /* if(i_AutoCalibration == FALSE) */ + if (s_BoardInfos[dev->minor].i_AutoCalibration == FALSE) { + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl((0x00001000 |i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00001000 | s_BoardInfos[dev->minor]. + i_ChannelNo), + devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 4); + data++; + i_APCI3200_ReadCalibrationOffsetValue(dev, data); + data++; + i_APCI3200_ReadCalibrationGainValue(dev, data); + } + } else { + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + 12) >> 19) & 1) != + 1) ; + /* outl((0x00000800|i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00000800 | s_BoardInfos[dev->minor].i_ChannelNo), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 4); + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 0); */ + ui_Configuration = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 0); + /*************************/ + /*Set the time unit to ns */ + /*************************/ + /* i_TimeUnit= i_ADDIDATAConversionTimeUnit; + i_ADDIDATAConversionTimeUnit= 1; */ + /* i_Temp= i_InterruptFlag ; */ + i_Temp = s_BoardInfos[dev->minor].i_InterruptFlag; + /* i_InterruptFlag = ADDIDATA_DISABLE; */ + s_BoardInfos[dev->minor].i_InterruptFlag = ADDIDATA_DISABLE; + i_APCI3200_Read1AnalogInputChannel(dev, s, insn, data); + /* if(i_AutoCalibration == FALSE) */ + if (s_BoardInfos[dev->minor].i_AutoCalibration == FALSE) { + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((0x00000800|i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00000800 | s_BoardInfos[dev->minor]. + i_ChannelNo), + devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 4); + data++; + i_APCI3200_ReadCalibrationOffsetValue(dev, data); + data++; + i_APCI3200_ReadCalibrationGainValue(dev, data); + } + } + /* i_InterruptFlag=i_Temp ; */ + s_BoardInfos[dev->minor].i_InterruptFlag = i_Temp; + /* printk("\ni_InterruptFlag=%d\n",i_InterruptFlag); */ + return insn->n; +} + +static int apci3200_ai_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + apci3200_reset(dev); + return insn->n; +} + +static int apci3200_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + + int err = 0; + unsigned int ui_ConvertTime = 0; + unsigned int ui_ConvertTimeBase = 0; + unsigned int ui_DelayTime = 0; + unsigned int ui_DelayTimeBase = 0; + int i_NbrOfChannel = 0; + int i_Cpt = 0; + double d_ConversionTimeForAllChannels = 0.0; + double d_SCANTimeNewUnit = 0.0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (s_BoardInfos[dev->minor].i_InterruptFlag == 0) + err |= -EINVAL; + + if (err) { + apci3200_reset(dev); + return 1; + } + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(&cmd->start_src); + err |= cfc_check_trigger_is_unique(&cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(&cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) { + apci3200_reset(dev); + return 2; + } + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + /* validate the trigger edge selection */ + arg = cmd->start_arg & 0xffff; + if (arg < 1 || arg > 3) { + cmd->start_arg &= ~0xffff; + cmd->start_arg |= 1; + err |= -EINVAL; + } + /* validate the trigger mode selection */ + arg = cmd->start_arg >> 16; + if (arg != 2) { + cmd->start_arg &= ~(0xffff << 16); + cmd->start_arg |= (2 << 16); + err |= -EINVAL; + } + break; + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + /* i_FirstChannel=cmd->chanlist[0]; */ + s_BoardInfos[dev->minor].i_FirstChannel = cmd->chanlist[0]; + /* i_LastChannel=cmd->chanlist[1]; */ + s_BoardInfos[dev->minor].i_LastChannel = cmd->chanlist[1]; + + if (cmd->convert_src == TRIG_TIMER) { + ui_ConvertTime = cmd->convert_arg & 0xFFFF; + ui_ConvertTimeBase = cmd->convert_arg >> 16; + if (ui_ConvertTime != 20 && ui_ConvertTime != 40 + && ui_ConvertTime != 80 && ui_ConvertTime != 160) + { + printk("\nThe selection of conversion time reload value is in error\n"); + err++; + } /* if (ui_ConvertTime!=20 && ui_ConvertTime!=40 && ui_ConvertTime!=80 && ui_ConvertTime!=160 ) */ + if (ui_ConvertTimeBase != 2) { + printk("\nThe selection of conversion time unit is in error\n"); + err++; + } /* if(ui_ConvertTimeBase!=2) */ + } else { + ui_ConvertTime = 0; + ui_ConvertTimeBase = 0; + } + if (cmd->scan_begin_src == TRIG_FOLLOW) { + ui_DelayTime = 0; + ui_DelayTimeBase = 0; + } /* if(cmd->scan_begin_src==TRIG_FOLLOW) */ + else { + ui_DelayTime = cmd->scan_begin_arg & 0xFFFF; + ui_DelayTimeBase = cmd->scan_begin_arg >> 16; + if (ui_DelayTimeBase != 2 && ui_DelayTimeBase != 3) { + err++; + printk("\nThe Delay time base selection is in error\n"); + } + if (ui_DelayTime < 1 || ui_DelayTime > 1023) { + err++; + printk("\nThe Delay time value is in error\n"); + } + if (err) { + apci3200_reset(dev); + return 3; + } + fpu_begin(); + d_SCANTimeNewUnit = (double)ui_DelayTime; + /* i_NbrOfChannel= i_LastChannel-i_FirstChannel + 4; */ + i_NbrOfChannel = + s_BoardInfos[dev->minor].i_LastChannel - + s_BoardInfos[dev->minor].i_FirstChannel + 4; + /**********************************************************/ + /*calculate the total conversion time for all the channels */ + /**********************************************************/ + d_ConversionTimeForAllChannels = + (double)((double)ui_ConvertTime / + (double)i_NbrOfChannel); + + /*******************************/ + /*Convert the frequence in time */ + /*******************************/ + d_ConversionTimeForAllChannels = + (double)1.0 / d_ConversionTimeForAllChannels; + ui_ConvertTimeBase = 3; + /***********************************/ + /*Test if the time unit is the same */ + /***********************************/ + + if (ui_DelayTimeBase <= ui_ConvertTimeBase) { + + for (i_Cpt = 0; + i_Cpt < (ui_ConvertTimeBase - ui_DelayTimeBase); + i_Cpt++) { + + d_ConversionTimeForAllChannels = + d_ConversionTimeForAllChannels * 1000; + d_ConversionTimeForAllChannels = + d_ConversionTimeForAllChannels + 1; + } + } else { + for (i_Cpt = 0; + i_Cpt < (ui_DelayTimeBase - ui_ConvertTimeBase); + i_Cpt++) { + d_SCANTimeNewUnit = d_SCANTimeNewUnit * 1000; + + } + } + + if (d_ConversionTimeForAllChannels >= d_SCANTimeNewUnit) { + + printk("\nSCAN Delay value cannot be used\n"); + /*********************************/ + /*SCAN Delay value cannot be used */ + /*********************************/ + err++; + } + fpu_end(); + } /* else if(cmd->scan_begin_src==TRIG_FOLLOW) */ + + if (err) { + apci3200_reset(dev); + return 4; + } + + return 0; +} + +static int apci3200_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct addi_private *devpriv = dev->private; + unsigned int ui_Configuration = 0; + + /* i_InterruptFlag=0; */ + /* i_Initialised=0; */ + /* i_Count=0; */ + /* i_Sum=0; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + + /*******************/ + /*Read the register */ + /*******************/ + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 8); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + /*****************************/ + /*Reset the START and IRQ bit */ + /*****************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Configuration & 0xFFE7FFFF),devpriv->iobase+i_Offset + 8); */ + outl((ui_Configuration & 0xFFE7FFFF), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + return 0; +} + +/* + * Does asynchronous acquisition + * Determines the mode 1 or 2. + */ +static int apci3200_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct addi_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int ui_Configuration = 0; + /* INT i_CurrentSource = 0; */ + unsigned int ui_Trigger = 0; + unsigned int ui_TriggerEdge = 0; + unsigned int ui_Triggermode = 0; + unsigned int ui_ScanMode = 0; + unsigned int ui_ConvertTime = 0; + unsigned int ui_ConvertTimeBase = 0; + unsigned int ui_DelayTime = 0; + unsigned int ui_DelayTimeBase = 0; + unsigned int ui_DelayMode = 0; + + /* i_FirstChannel=cmd->chanlist[0]; */ + /* i_LastChannel=cmd->chanlist[1]; */ + s_BoardInfos[dev->minor].i_FirstChannel = cmd->chanlist[0]; + s_BoardInfos[dev->minor].i_LastChannel = cmd->chanlist[1]; + if (cmd->start_src == TRIG_EXT) { + ui_Trigger = 1; + ui_TriggerEdge = cmd->start_arg & 0xFFFF; + ui_Triggermode = cmd->start_arg >> 16; + } /* if(cmd->start_src==TRIG_EXT) */ + else { + ui_Trigger = 0; + } /* elseif(cmd->start_src==TRIG_EXT) */ + + if (cmd->stop_src == TRIG_COUNT) { + ui_ScanMode = 0; + } /* if (cmd->stop_src==TRIG_COUNT) */ + else { + ui_ScanMode = 2; + } /* else if (cmd->stop_src==TRIG_COUNT) */ + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + ui_DelayTime = 0; + ui_DelayTimeBase = 0; + ui_DelayMode = 0; + } /* if(cmd->scan_begin_src==TRIG_FOLLOW) */ + else { + ui_DelayTime = cmd->scan_begin_arg & 0xFFFF; + ui_DelayTimeBase = cmd->scan_begin_arg >> 16; + ui_DelayMode = 1; + } /* else if(cmd->scan_begin_src==TRIG_FOLLOW) */ + /* printk("\nui_DelayTime=%u\n",ui_DelayTime); */ + /* printk("\nui_DelayTimeBase=%u\n",ui_DelayTimeBase); */ + if (cmd->convert_src == TRIG_TIMER) { + ui_ConvertTime = cmd->convert_arg & 0xFFFF; + ui_ConvertTimeBase = cmd->convert_arg >> 16; + } else { + ui_ConvertTime = 0; + ui_ConvertTimeBase = 0; + } + + /* if(i_ADDIDATAType ==1 || ((i_ADDIDATAType==2))) */ + /* { */ + /**************************************************/ + /*Read the old configuration of the current source */ + /**************************************************/ + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 12); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /***********************************************/ + /*Write the configuration of the current source */ + /***********************************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Configuration & 0xFFC00000 ), devpriv->iobase+i_Offset +12); */ + outl((ui_Configuration & 0xFFC00000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /* } */ + ui_Configuration = 0; + /* printk("\nfirstchannel=%u\n",i_FirstChannel); */ + /* printk("\nlastchannel=%u\n",i_LastChannel); */ + /* printk("\nui_Trigger=%u\n",ui_Trigger); */ + /* printk("\nui_TriggerEdge=%u\n",ui_TriggerEdge); */ + /* printk("\nui_Triggermode=%u\n",ui_Triggermode); */ + /* printk("\nui_DelayMode=%u\n",ui_DelayMode); */ + /* printk("\nui_ScanMode=%u\n",ui_ScanMode); */ + + /* ui_Configuration = i_FirstChannel |(i_LastChannel << 8)| 0x00100000 | */ + ui_Configuration = + s_BoardInfos[dev->minor].i_FirstChannel | (s_BoardInfos[dev-> + minor]. + i_LastChannel << 8) | 0x00100000 | (ui_Trigger << 24) | + (ui_TriggerEdge << 25) | (ui_Triggermode << 27) | (ui_DelayMode + << 18) | (ui_ScanMode << 16); + + /*************************/ + /*Write the Configuration */ + /*************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl( ui_Configuration, devpriv->iobase+i_Offset + 0x8); */ + outl(ui_Configuration, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x8); + /***********************/ + /*Write the Delay Value */ + /***********************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_DelayTime,devpriv->iobase+i_Offset + 40); */ + outl(ui_DelayTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 40); + /***************************/ + /*Write the Delay time base */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_DelayTimeBase,devpriv->iobase+i_Offset + 44); */ + outl(ui_DelayTimeBase, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 44); + /*********************************/ + /*Write the conversion time value */ + /*********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_ConvertTime,devpriv->iobase+i_Offset + 32); */ + outl(ui_ConvertTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + + /********************************/ + /*Write the conversion time base */ + /********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_ConvertTimeBase,devpriv->iobase+i_Offset + 36); */ + outl(ui_ConvertTimeBase, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /*******************/ + /*Read the register */ + /*******************/ + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 4); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /******************/ + /*Set the SCAN bit */ + /******************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(((ui_Configuration & 0x1E0FF) | 0x00002000),devpriv->iobase+i_Offset + 4); */ + outl(((ui_Configuration & 0x1E0FF) | 0x00002000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*******************/ + /*Read the register */ + /*******************/ + ui_Configuration = 0; + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 8); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*******************/ + /*Set the START bit */ + /*******************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Configuration | 0x00080000),devpriv->iobase+i_Offset + 8); */ + outl((ui_Configuration | 0x00080000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + return 0; +} + +/* + * This function copies the acquired data(from FIFO) to Comedi buffer. + */ +static int i_APCI3200_InterruptHandleEos(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int ui_StatusRegister = 0; + + /* BEGIN JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* comedi_async *async = s->async; */ + /* UINT *data; */ + /* data=async->data+async->buf_int_ptr;//new samples added from here onwards */ + int n = 0, i = 0; + /* END JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /************************************/ + /*Read the interrupt status register */ + /************************************/ + /* ui_StatusRegister = inl(devpriv->iobase+i_Offset + 16); */ + ui_StatusRegister = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 16); + + /*************************/ + /*Test if interrupt occur */ + /*************************/ + + if ((ui_StatusRegister & 0x2) == 0x2) { + /*************************/ + /*Read the channel number */ + /*************************/ + /* ui_ChannelNumber = inl(devpriv->iobase+i_Offset + 24); */ + /* BEGIN JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* This value is not used */ + /* ui_ChannelNumber = inl(devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 24); */ + /* END JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /*************************************/ + /*Read the digital Analog Input value */ + /*************************************/ + + /* data[i_Count] = inl(devpriv->iobase+i_Offset + 28); */ + /* Begin JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* data[s_BoardInfos [dev->minor].i_Count] = inl(devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 28); */ + s_BoardInfos[dev->minor].ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /* if((i_Count == (i_LastChannel-i_FirstChannel+3))) */ + if ((s_BoardInfos[dev->minor].i_Count == + (s_BoardInfos[dev->minor].i_LastChannel - + s_BoardInfos[dev->minor]. + i_FirstChannel + 3))) { + + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + s_BoardInfos[dev->minor].i_Count++; + + for (i = s_BoardInfos[dev->minor].i_FirstChannel; + i <= s_BoardInfos[dev->minor].i_LastChannel; + i++) { + i_APCI3200_GetChannelCalibrationValue(dev, i, + &s_BoardInfos[dev->minor]. + ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count + ((i - + s_BoardInfos + [dev->minor]. + i_FirstChannel) + * 3)], + &s_BoardInfos[dev->minor]. + ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count + ((i - + s_BoardInfos + [dev->minor]. + i_FirstChannel) + * 3) + 1], + &s_BoardInfos[dev->minor]. + ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count + ((i - + s_BoardInfos + [dev->minor]. + i_FirstChannel) + * 3) + 2]); + } + + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* i_Count=-1; */ + + s_BoardInfos[dev->minor].i_Count = -1; + + /* async->buf_int_count+=(i_LastChannel-i_FirstChannel+4)*sizeof(unsigned int); */ + /* Begin JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* async->buf_int_count+=(s_BoardInfos [dev->minor].i_LastChannel-s_BoardInfos [dev->minor].i_FirstChannel+4)*sizeof(unsigned int); */ + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* async->buf_int_ptr+=(i_LastChannel-i_FirstChannel+4)*sizeof(unsigned int); */ + /* Begin JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* async->buf_int_ptr+=(s_BoardInfos [dev->minor].i_LastChannel-s_BoardInfos [dev->minor].i_FirstChannel+4)*sizeof(unsigned int); */ + /* comedi_eos(dev,s); */ + + /* Set the event type (Comedi Buffer End Of Scan) */ + s->async->events |= COMEDI_CB_EOS; + + /* Test if enougth memory is available and allocate it for 7 values */ + n = comedi_buf_write_alloc(s, + (7 + 12) * sizeof(unsigned int)); + + /* If not enough memory available, event is set to Comedi Buffer Error */ + if (n > ((7 + 12) * sizeof(unsigned int))) { + printk("\ncomedi_buf_write_alloc n = %i", n); + s->async->events |= COMEDI_CB_ERROR; + } + /* Write all 7 scan values in the comedi buffer */ + comedi_buf_memcpy_to(s, 0, + (unsigned int *) s_BoardInfos[dev->minor]. + ui_ScanValueArray, (7 + 12) * sizeof(unsigned int)); + + /* Update comedi buffer pinters indexes */ + comedi_buf_write_free(s, + (7 + 12) * sizeof(unsigned int)); + + /* Send events */ + comedi_event(dev, s); + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /* BEGIN JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* */ + /* if (s->async->buf_int_ptr>=s->async->data_len) // for buffer rool over */ + /* { */ + /* /* buffer rollover */ */ + /* s->async->buf_int_ptr=0; */ + /* comedi_eobuf(dev,s); */ + /* } */ + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + } + /* i_Count++; */ + s_BoardInfos[dev->minor].i_Count++; + } + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + return 0; +} + +static void apci3200_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct addi_private *devpriv = dev->private; + unsigned int ui_StatusRegister = 0; + unsigned int ui_ChannelNumber = 0; + int i_CalibrationFlag = 0; + int i_CJCFlag = 0; + unsigned int ui_DummyValue = 0; + unsigned int ui_DigitalTemperature = 0; + unsigned int ui_DigitalInput = 0; + int i_ConvertCJCCalibration; + /* BEGIN JK TEST */ + int i_ReturnValue = 0; + /* END JK TEST */ + + /* printk ("\n i_ScanType = %i i_ADDIDATAType = %i", s_BoardInfos [dev->minor].i_ScanType, s_BoardInfos [dev->minor].i_ADDIDATAType); */ + + /* switch(i_ScanType) */ + switch (s_BoardInfos[dev->minor].i_ScanType) { + case 0: + case 1: + /* switch(i_ADDIDATAType) */ + switch (s_BoardInfos[dev->minor].i_ADDIDATAType) { + case 0: + case 1: + + /************************************/ + /*Read the interrupt status register */ + /************************************/ + /* ui_StatusRegister = inl(devpriv->iobase+i_Offset + 16); */ + ui_StatusRegister = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 16); + if ((ui_StatusRegister & 0x2) == 0x2) { + /* i_CalibrationFlag = ((inl(devpriv->iobase+i_Offset + 12) & 0x00060000) >> 17); */ + i_CalibrationFlag = + ((inl(devpriv->iobase + + s_BoardInfos[dev-> + minor]. + i_Offset + + 12) & 0x00060000) >> + 17); + /*************************/ + /*Read the channel number */ + /*************************/ + /* ui_ChannelNumber = inl(devpriv->iobase+i_Offset + 24); */ + + /*************************************/ + /*Read the digital analog input value */ + /*************************************/ + /* ui_DigitalInput = inl(devpriv->iobase+i_Offset + 28); */ + ui_DigitalInput = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + /***********************************************/ + /* Test if the value read is the channel value */ + /***********************************************/ + if (i_CalibrationFlag == 0) { + /* ui_InterruptChannelValue[i_Count + 0] = ui_DigitalInput; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 0] = ui_DigitalInput; + + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* + printk("\n 1 - i_APCI3200_GetChannelCalibrationValue (dev, s_BoardInfos %i", ui_ChannelNumber); + i_APCI3200_GetChannelCalibrationValue (dev, s_BoardInfos [dev->minor].ui_Channel_num, + &s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count + 6], + &s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count + 7], + &s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count + 8]); + */ + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /******************************************************/ + /*Start the conversion of the calibration offset value */ + /******************************************************/ + i_APCI3200_ReadCalibrationOffsetValue + (dev, &ui_DummyValue); + } /* if (i_CalibrationFlag == 0) */ + /**********************************************************/ + /* Test if the value read is the calibration offset value */ + /**********************************************************/ + + if (i_CalibrationFlag == 1) { + + /******************/ + /* Save the value */ + /******************/ + + /* ui_InterruptChannelValue[i_Count + 1] = ui_DigitalInput; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 1] = ui_DigitalInput; + + /******************************************************/ + /* Start the conversion of the calibration gain value */ + /******************************************************/ + i_APCI3200_ReadCalibrationGainValue(dev, + &ui_DummyValue); + } /* if (i_CalibrationFlag == 1) */ + /******************************************************/ + /*Test if the value read is the calibration gain value */ + /******************************************************/ + + if (i_CalibrationFlag == 2) { + + /****************/ + /*Save the value */ + /****************/ + /* ui_InterruptChannelValue[i_Count + 2] = ui_DigitalInput; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 2] = ui_DigitalInput; + /* if(i_ScanType==1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType == 1) { + + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor]. + i_InterruptFlag = 0; + /* i_Count=i_Count + 6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count + 6; */ + s_BoardInfos[dev->minor]. + i_Count = + s_BoardInfos[dev-> + minor].i_Count + 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } /* if(i_ScanType==1) */ + else { + /* i_Count=0; */ + s_BoardInfos[dev->minor]. + i_Count = 0; + } /* elseif(i_ScanType==1) */ + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType != 1) { + i_ReturnValue = send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } /* if(i_ScanType!=1) */ + else { + /* if(i_ChannelCount==i_Sum) */ + if (s_BoardInfos[dev->minor]. + i_ChannelCount == + s_BoardInfos[dev-> + minor].i_Sum) { + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } + } /* if(i_ScanType!=1) */ + } /* if (i_CalibrationFlag == 2) */ + } /* if ((ui_StatusRegister & 0x2) == 0x2) */ + + break; + + case 2: + /************************************/ + /*Read the interrupt status register */ + /************************************/ + + /* ui_StatusRegister = inl(devpriv->iobase+i_Offset + 16); */ + ui_StatusRegister = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 16); + /*************************/ + /*Test if interrupt occur */ + /*************************/ + + if ((ui_StatusRegister & 0x2) == 0x2) { + + /* i_CJCFlag = ((inl(devpriv->iobase+i_Offset + 4) & 0x00000400) >> 10); */ + i_CJCFlag = + ((inl(devpriv->iobase + + s_BoardInfos[dev-> + minor]. + i_Offset + + 4) & 0x00000400) >> 10); + + /* i_CalibrationFlag = ((inl(devpriv->iobase+i_Offset + 12) & 0x00060000) >> 17); */ + i_CalibrationFlag = + ((inl(devpriv->iobase + + s_BoardInfos[dev-> + minor]. + i_Offset + + 12) & 0x00060000) >> + 17); + + /*************************/ + /*Read the channel number */ + /*************************/ + + /* ui_ChannelNumber = inl(devpriv->iobase+i_Offset + 24); */ + ui_ChannelNumber = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 24); + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + s_BoardInfos[dev->minor].ui_Channel_num = + ui_ChannelNumber; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /************************************/ + /*Read the digital temperature value */ + /************************************/ + /* ui_DigitalTemperature = inl(devpriv->iobase+i_Offset + 28); */ + ui_DigitalTemperature = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + /*********************************************/ + /*Test if the value read is the channel value */ + /*********************************************/ + + if ((i_CalibrationFlag == 0) + && (i_CJCFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 0]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 0] = + ui_DigitalTemperature; + + /*********************************/ + /*Start the conversion of the CJC */ + /*********************************/ + i_APCI3200_ReadCJCValue(dev, + &ui_DummyValue); + + } /* if ((i_CalibrationFlag == 0) && (i_CJCFlag == 0)) */ + + /*****************************************/ + /*Test if the value read is the CJC value */ + /*****************************************/ + + if ((i_CJCFlag == 1) + && (i_CalibrationFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 3]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 3] = + ui_DigitalTemperature; + + /******************************************************/ + /*Start the conversion of the calibration offset value */ + /******************************************************/ + i_APCI3200_ReadCalibrationOffsetValue + (dev, &ui_DummyValue); + } /* if ((i_CJCFlag == 1) && (i_CalibrationFlag == 0)) */ + + /********************************************************/ + /*Test if the value read is the calibration offset value */ + /********************************************************/ + + if ((i_CalibrationFlag == 1) + && (i_CJCFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 1]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 1] = + ui_DigitalTemperature; + + /****************************************************/ + /*Start the conversion of the calibration gain value */ + /****************************************************/ + i_APCI3200_ReadCalibrationGainValue(dev, + &ui_DummyValue); + + } /* if ((i_CalibrationFlag == 1) && (i_CJCFlag == 0)) */ + + /******************************************************/ + /*Test if the value read is the calibration gain value */ + /******************************************************/ + + if ((i_CalibrationFlag == 2) + && (i_CJCFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 2]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 2] = + ui_DigitalTemperature; + + /**********************************************************/ + /*Test if the Calibration channel must be read for the CJC */ + /**********************************************************/ + + /*Test if the polarity is the same */ + /**********************************/ + /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (s_BoardInfos[dev->minor]. + i_CJCPolarity != + s_BoardInfos[dev->minor]. + i_ADDIDATAPolarity) { + i_ConvertCJCCalibration = 1; + } /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + else { + /* if(i_CJCGain==i_ADDIDATAGain) */ + if (s_BoardInfos[dev->minor]. + i_CJCGain == + s_BoardInfos[dev-> + minor]. + i_ADDIDATAGain) { + i_ConvertCJCCalibration + = 0; + } /* if(i_CJCGain==i_ADDIDATAGain) */ + else { + i_ConvertCJCCalibration + = 1; + } /* elseif(i_CJCGain==i_ADDIDATAGain) */ + } /* elseif(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (i_ConvertCJCCalibration == 1) { + /****************************************************************/ + /*Start the conversion of the calibration gain value for the CJC */ + /****************************************************************/ + i_APCI3200_ReadCJCCalOffset(dev, + &ui_DummyValue); + + } /* if(i_ConvertCJCCalibration==1) */ + else { + /* ui_InterruptChannelValue[i_Count + 4]=0; */ + /* ui_InterruptChannelValue[i_Count + 5]=0; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev-> + minor].i_Count + + 4] = 0; + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev-> + minor].i_Count + + 5] = 0; + } /* elseif(i_ConvertCJCCalibration==1) */ + } /* else if ((i_CalibrationFlag == 2) && (i_CJCFlag == 0)) */ + + /********************************************************************/ + /*Test if the value read is the calibration offset value for the CJC */ + /********************************************************************/ + + if ((i_CalibrationFlag == 1) + && (i_CJCFlag == 1)) { + /* ui_InterruptChannelValue[i_Count + 4]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 4] = + ui_DigitalTemperature; + + /****************************************************************/ + /*Start the conversion of the calibration gain value for the CJC */ + /****************************************************************/ + i_APCI3200_ReadCJCCalGain(dev, + &ui_DummyValue); + + } /* if ((i_CalibrationFlag == 1) && (i_CJCFlag == 1)) */ + + /******************************************************************/ + /*Test if the value read is the calibration gain value for the CJC */ + /******************************************************************/ + + if ((i_CalibrationFlag == 2) + && (i_CJCFlag == 1)) { + /* ui_InterruptChannelValue[i_Count + 5]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 5] = + ui_DigitalTemperature; + + /* if(i_ScanType==1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType == 1) { + + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor]. + i_InterruptFlag = 0; + /* i_Count=i_Count + 6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count + 6; */ + s_BoardInfos[dev->minor]. + i_Count = + s_BoardInfos[dev-> + minor].i_Count + 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } /* if(i_ScanType==1) */ + else { + /* i_Count=0; */ + s_BoardInfos[dev->minor]. + i_Count = 0; + } /* elseif(i_ScanType==1) */ + + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType != 1) { + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } /* if(i_ScanType!=1) */ + else { + /* if(i_ChannelCount==i_Sum) */ + if (s_BoardInfos[dev->minor]. + i_ChannelCount == + s_BoardInfos[dev-> + minor].i_Sum) { + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + + } /* if(i_ChannelCount==i_Sum) */ + } /* else if(i_ScanType!=1) */ + } /* if ((i_CalibrationFlag == 2) && (i_CJCFlag == 1)) */ + + } /* else if ((ui_StatusRegister & 0x2) == 0x2) */ + break; + } /* switch(i_ADDIDATAType) */ + break; + case 2: + case 3: + i_APCI3200_InterruptHandleEos(dev); + break; + } /* switch(i_ScanType) */ + return; +} 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 00000000000..e82c3fcd048 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c @@ -0,0 +1,174 @@ +/* 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; + int i_Temp; + + 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); + } + } + + i_Temp = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1; + 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)) { + printk("\nIn ReadTimerCounterWatchdog :: Invalid Subdevice \n"); + } + return insn->n; +} diff --git a/drivers/staging/comedi/drivers/addi_apci_035.c b/drivers/staging/comedi/drivers/addi_apci_035.c new file mode 100644 index 00000000000..4da9db35b8e --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_035.c @@ -0,0 +1,77 @@ +#include <linux/module.h> +#include <linux/pci.h> + +#include "../comedidev.h" +#include "comedi_fc.h" +#include "amcc_s5933.h" + +#include "addi-data/addi_common.h" + +#define ADDIDATA_WATCHDOG 2 /* Or shold it be something else */ + +#include "addi-data/addi_eeprom.c" +#include "addi-data/hwdrv_apci035.c" +#include "addi-data/addi_common.c" + +static const struct addi_board apci035_boardtypes[] = { + { + .pc_DriverName = "apci035", + .i_IorangeBase1 = APCI035_ADDRESS_RANGE, + .i_PCIEeprom = 1, + .pc_EepromChip = ADDIDATA_S5920, + .i_NbrAiChannel = 16, + .i_NbrAiChannelDiff = 8, + .i_AiChannelList = 16, + .i_AiMaxdata = 0xff, + .pr_AiRangelist = &range_apci035_ai, + .i_Timer = 1, + .ui_MinAcquisitiontimeNs = 10000, + .ui_MinDelaytimeNs = 100000, + .interrupt = apci035_interrupt, + .reset = apci035_reset, + .ai_config = apci035_ai_config, + .ai_read = apci035_ai_read, + .timer_config = apci035_timer_config, + .timer_write = apci035_timer_write, + .timer_read = apci035_timer_read, + }, +}; + +static int apci035_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + dev->board_ptr = &apci035_boardtypes[0]; + + return addi_auto_attach(dev, context); +} + +static struct comedi_driver apci035_driver = { + .driver_name = "addi_apci_035", + .module = THIS_MODULE, + .auto_attach = apci035_auto_attach, + .detach = i_ADDI_Detach, +}; + +static int apci035_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci035_driver, id->driver_data); +} + +static const struct pci_device_id apci035_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x0300) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci035_pci_table); + +static struct pci_driver apci035_pci_driver = { + .name = "addi_apci_035", + .id_table = apci035_pci_table, + .probe = apci035_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci035_driver, apci035_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_apci_1032.c b/drivers/staging/comedi/drivers/addi_apci_1032.c new file mode 100644 index 00000000000..1b2e7c040c9 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1032.c @@ -0,0 +1,384 @@ +/* + * 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. + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" +#include "comedi_fc.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; +} + +/* + * 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 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + 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_put(s, s->state); + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + comedi_event(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); + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(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 00000000000..eab75eb2647 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1500.c @@ -0,0 +1,76 @@ +#include <linux/module.h> +#include <linux/pci.h> + +#include "../comedidev.h" +#include "comedi_fc.h" +#include "amcc_s5933.h" + +#include "addi-data/addi_common.h" + +#include "addi-data/addi_eeprom.c" +#include "addi-data/hwdrv_apci1500.c" +#include "addi-data/addi_common.c" + +static const struct addi_board apci1500_boardtypes[] = { + { + .pc_DriverName = "apci1500", + .i_IorangeBase1 = APCI1500_ADDRESS_RANGE, + .i_PCIEeprom = ADDIDATA_NO_EEPROM, + .i_NbrDiChannel = 16, + .i_NbrDoChannel = 16, + .i_DoMaxdata = 0xffff, + .i_Timer = 1, + .interrupt = apci1500_interrupt, + .reset = apci1500_reset, + .di_config = apci1500_di_config, + .di_read = apci1500_di_read, + .di_write = apci1500_di_write, + .di_bits = apci1500_di_insn_bits, + .do_config = apci1500_do_config, + .do_write = apci1500_do_write, + .do_bits = apci1500_do_bits, + .timer_config = apci1500_timer_config, + .timer_write = apci1500_timer_write, + .timer_read = apci1500_timer_read, + .timer_bits = apci1500_timer_bits, + }, +}; + +static int apci1500_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + dev->board_ptr = &apci1500_boardtypes[0]; + + return addi_auto_attach(dev, context); +} + +static struct comedi_driver apci1500_driver = { + .driver_name = "addi_apci_1500", + .module = THIS_MODULE, + .auto_attach = apci1500_auto_attach, + .detach = i_ADDI_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 00000000000..e9c5291c77c --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1516.c @@ -0,0 +1,227 @@ +/* + * 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 <linux/pci.h> + +#include "../comedidev.h" +#include "addi_watchdog.h" +#include "comedi_fc.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 = comedi_board(dev); + 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_WRITEABLE; + 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_disable(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 00000000000..13d9962b47e --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1564.c @@ -0,0 +1,192 @@ +#include <linux/module.h> +#include <linux/pci.h> + +#include "../comedidev.h" +#include "comedi_fc.h" + +#include "addi-data/addi_common.h" + +#include "addi-data/hwdrv_apci1564.c" + +static irqreturn_t v_ADDI_Interrupt(int irq, void *d) +{ + apci1564_interrupt(irq, d); + return IRQ_RETVAL(1); +} + +static int apci1564_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_private *devpriv = dev->private; + + data[1] = inl(devpriv->i_IobaseAmcc + 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) +{ + struct addi_private *devpriv = dev->private; + + s->state = inl(devpriv->i_IobaseAmcc + APCI1564_DO_REG); + + if (comedi_dio_update_state(s, data)) + outl(s->state, devpriv->i_IobaseAmcc + APCI1564_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int apci1564_reset(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + + ui_Type = 0; + + /* Disable the input interrupts and reset status register */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG); + inl(devpriv->i_IobaseAmcc + APCI1564_DI_INT_STATUS_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE1_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE2_REG); + + /* Reset the output channels and disable interrupts */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG); + + /* Reset the watchdog registers */ + addi_watchdog_reset(devpriv->i_IobaseAmcc + APCI1564_WDOG_REG); + + /* Reset the timer registers */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_RELOAD_REG); + + /* Reset the counter registers */ + outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1)); + outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2)); + outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3)); + outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4)); + + 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 addi_private *devpriv; + struct comedi_subdevice *s; + int ret; + + dev->board_name = dev->driver->driver_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->i_IobaseAmcc = pci_resource_start(pcidev, 0); + + apci1564_reset(dev); + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, v_ADDI_Interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 3); + 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->len_chanlist = 32; + s->range_table = &range_digital; + s->insn_config = apci1564_di_config; + 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_WRITEABLE; + s->n_chan = 32; + s->maxdata = 0xffffffff; + s->len_chanlist = 32; + s->range_table = &range_digital; + s->insn_config = apci1564_do_config; + s->insn_bits = apci1564_do_insn_bits; + s->insn_read = apci1564_do_read; + + /* Allocate and Initialise Timer Subdevice Structures */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 1; + s->maxdata = 0; + s->len_chanlist = 1; + s->range_table = &range_digital; + s->insn_write = apci1564_timer_write; + s->insn_read = apci1564_timer_read; + s->insn_config = apci1564_timer_config; + + return 0; +} + +static void apci1564_detach(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + + if (devpriv) { + if (dev->iobase) + apci1564_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + } + comedi_pci_disable(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 00000000000..28df4b50b87 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_16xx.c @@ -0,0 +1,188 @@ +/* + * 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 <linux/pci.h> + +#include "../comedidev.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_WRITEABLE | 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_disable, +}; + +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 00000000000..be0a8a7bd3b --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_2032.c @@ -0,0 +1,378 @@ +/* + * 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/pci.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +#include "../comedidev.h" +#include "addi_watchdog.h" +#include "comedi_fc.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; + unsigned int stop_count; + 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 bool apci2032_int_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char enabled_isns) +{ + struct apci2032_int_private *subpriv = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + bool do_event; + + subpriv->enabled_isns = enabled_isns; + subpriv->stop_count = cmd->stop_arg; + if (cmd->stop_src == TRIG_COUNT && subpriv->stop_count == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + subpriv->active = false; + do_event = true; + } else { + subpriv->active = true; + outl(enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG); + do_event = false; + } + + return do_event; +} + +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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + if (cmd->stop_src == TRIG_NONE) + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + 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; + bool do_event; + + 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); + do_event = apci2032_int_start(dev, s, enabled_isns); + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + if (do_event) + comedi_event(dev, s); + + 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; + bool do_event = false; + + 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); + } + + if (comedi_buf_put(s, bits)) { + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + if (cmd->stop_src == TRIG_COUNT && + subpriv->stop_count > 0) { + subpriv->stop_count--; + if (subpriv->stop_count == 0) { + /* end of acquisition */ + s->async->events |= COMEDI_CB_EOA; + apci2032_int_stop(dev, s); + } + } + } else { + apci2032_int_stop(dev, s); + s->async->events |= COMEDI_CB_OVERFLOW; + } + do_event = true; + } + + spin_unlock(&subpriv->spinlock); + if (do_event) + comedi_event(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_WRITEABLE; + 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; + 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); + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->read_subdev) + kfree(dev->read_subdev->private); + comedi_pci_disable(dev); +} + +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 00000000000..e1a916546d1 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_2200.c @@ -0,0 +1,153 @@ +/* + * 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 <linux/pci.h> + +#include "../comedidev.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_WRITEABLE; + 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_disable(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 00000000000..0cfb12fa1cb --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3120.c @@ -0,0 +1,250 @@ +#include <linux/module.h> +#include <linux/pci.h> + +#include "../comedidev.h" +#include "comedi_fc.h" +#include "amcc_s5933.h" + +#include "addi-data/addi_common.h" + +#include "addi-data/hwdrv_apci3120.c" + +enum apci3120_boardid { + BOARD_APCI3120, + BOARD_APCI3001, +}; + +static const struct addi_board apci3120_boardtypes[] = { + [BOARD_APCI3120] = { + .pc_DriverName = "apci3120", + .i_NbrAiChannel = 16, + .i_NbrAiChannelDiff = 8, + .i_AiChannelList = 16, + .i_NbrAoChannel = 8, + .i_AiMaxdata = 0xffff, + .i_AoMaxdata = 0x3fff, + .i_NbrDiChannel = 4, + .i_NbrDoChannel = 4, + .i_DoMaxdata = 0x0f, + .interrupt = apci3120_interrupt, + }, + [BOARD_APCI3001] = { + .pc_DriverName = "apci3001", + .i_NbrAiChannel = 16, + .i_NbrAiChannelDiff = 8, + .i_AiChannelList = 16, + .i_AiMaxdata = 0xfff, + .i_NbrDiChannel = 4, + .i_NbrDoChannel = 4, + .i_DoMaxdata = 0x0f, + .interrupt = apci3120_interrupt, + }, +}; + +static irqreturn_t v_ADDI_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + const struct addi_board *this_board = comedi_board(dev); + + this_board->interrupt(irq, d); + return IRQ_RETVAL(1); +} + +static int apci3120_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct addi_board *this_board = NULL; + struct addi_private *devpriv; + struct comedi_subdevice *s; + int ret, pages, i; + + 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->pc_DriverName; + + 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->iobase = dev->iobase; + devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0); + devpriv->i_IobaseAddon = pci_resource_start(pcidev, 2); + devpriv->i_IobaseReserved = pci_resource_start(pcidev, 3); + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, v_ADDI_Interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + devpriv->us_UseDma = ADDI_ENABLE; + + /* Allocate DMA buffers */ + devpriv->b_DmaDoubleBuffer = 0; + for (i = 0; i < 2; i++) { + for (pages = 4; pages >= 0; pages--) { + devpriv->ul_DmaBufferVirtual[i] = + (void *) __get_free_pages(GFP_KERNEL, pages); + + if (devpriv->ul_DmaBufferVirtual[i]) + break; + } + if (devpriv->ul_DmaBufferVirtual[i]) { + devpriv->ui_DmaBufferPages[i] = pages; + devpriv->ui_DmaBufferSize[i] = PAGE_SIZE * pages; + devpriv->ul_DmaBufferHw[i] = + virt_to_bus((void *)devpriv-> + ul_DmaBufferVirtual[i]); + } + } + if (!devpriv->ul_DmaBufferVirtual[0]) + devpriv->us_UseDma = ADDI_DISABLE; + + if (devpriv->ul_DmaBufferVirtual[1]) + devpriv->b_DmaDoubleBuffer = 1; + + ret = comedi_alloc_subdevices(dev, 5); + if (ret) + return ret; + + /* Allocate and Initialise AI Subdevice Structures */ + s = &dev->subdevices[0]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_COMMON | SDF_GROUND + | SDF_DIFF; + if (this_board->i_NbrAiChannel) { + s->n_chan = this_board->i_NbrAiChannel; + devpriv->b_SingelDiff = 0; + } else { + s->n_chan = this_board->i_NbrAiChannelDiff; + devpriv->b_SingelDiff = 1; + } + s->maxdata = this_board->i_AiMaxdata; + s->len_chanlist = this_board->i_AiChannelList; + s->range_table = &range_apci3120_ai; + + s->insn_config = apci3120_ai_insn_config; + s->insn_read = apci3120_ai_insn_read; + s->do_cmdtest = apci3120_ai_cmdtest; + s->do_cmd = apci3120_ai_cmd; + s->cancel = apci3120_cancel; + + /* Allocate and Initialise AO Subdevice Structures */ + s = &dev->subdevices[1]; + if (this_board->i_NbrAoChannel) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->i_NbrAoChannel; + s->maxdata = this_board->i_AoMaxdata; + s->len_chanlist = this_board->i_NbrAoChannel; + s->range_table = &range_apci3120_ao; + s->insn_write = apci3120_ao_insn_write; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Allocate and Initialise DI Subdevice Structures */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->i_NbrDiChannel; + s->maxdata = 1; + s->len_chanlist = this_board->i_NbrDiChannel; + s->range_table = &range_digital; + s->insn_bits = apci3120_di_insn_bits; + + /* Allocate and Initialise DO Subdevice Structures */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = + SDF_READABLE | SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->i_NbrDoChannel; + s->maxdata = this_board->i_DoMaxdata; + s->len_chanlist = this_board->i_NbrDoChannel; + s->range_table = &range_digital; + s->insn_bits = apci3120_do_insn_bits; + + /* Allocate and Initialise Timer Subdevice Structures */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 1; + s->maxdata = 0; + s->len_chanlist = 1; + s->range_table = &range_digital; + + s->insn_write = apci3120_write_insn_timer; + s->insn_read = apci3120_read_insn_timer; + s->insn_config = apci3120_config_insn_timer; + + apci3120_reset(dev); + return 0; +} + +static void apci3120_detach(struct comedi_device *dev) +{ + struct addi_private *devpriv = dev->private; + + if (devpriv) { + if (dev->iobase) + apci3120_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv->ul_DmaBufferVirtual[0]) { + free_pages((unsigned long)devpriv-> + ul_DmaBufferVirtual[0], + devpriv->ui_DmaBufferPages[0]); + } + if (devpriv->ul_DmaBufferVirtual[1]) { + free_pages((unsigned long)devpriv-> + ul_DmaBufferVirtual[1], + devpriv->ui_DmaBufferPages[1]); + } + } + comedi_pci_disable(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_3200.c b/drivers/staging/comedi/drivers/addi_apci_3200.c new file mode 100644 index 00000000000..f0f891a482a --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3200.c @@ -0,0 +1,125 @@ +#include <linux/module.h> +#include <linux/pci.h> + +#include <asm/i387.h> + +#include "../comedidev.h" +#include "comedi_fc.h" +#include "amcc_s5933.h" + +#include "addi-data/addi_common.h" + +static void fpu_begin(void) +{ + kernel_fpu_begin(); +} + +static void fpu_end(void) +{ + kernel_fpu_end(); +} + +#include "addi-data/addi_eeprom.c" +#include "addi-data/hwdrv_apci3200.c" +#include "addi-data/addi_common.c" + +enum apci3200_boardid { + BOARD_APCI3200, + BOARD_APCI3300, +}; + +static const struct addi_board apci3200_boardtypes[] = { + [BOARD_APCI3200] = { + .pc_DriverName = "apci3200", + .i_IorangeBase1 = 256, + .i_PCIEeprom = ADDIDATA_EEPROM, + .pc_EepromChip = ADDIDATA_S5920, + .i_NbrAiChannel = 16, + .i_NbrAiChannelDiff = 8, + .i_AiChannelList = 16, + .i_AiMaxdata = 0x3ffff, + .pr_AiRangelist = &range_apci3200_ai, + .i_NbrDiChannel = 4, + .i_NbrDoChannel = 4, + .ui_MinAcquisitiontimeNs = 10000, + .ui_MinDelaytimeNs = 100000, + .interrupt = apci3200_interrupt, + .reset = apci3200_reset, + .ai_config = apci3200_ai_config, + .ai_read = apci3200_ai_read, + .ai_write = apci3200_ai_write, + .ai_bits = apci3200_ai_bits_test, + .ai_cmdtest = apci3200_ai_cmdtest, + .ai_cmd = apci3200_ai_cmd, + .ai_cancel = apci3200_cancel, + .di_bits = apci3200_di_insn_bits, + .do_bits = apci3200_do_insn_bits, + }, + [BOARD_APCI3300] = { + .pc_DriverName = "apci3300", + .i_IorangeBase1 = 256, + .i_PCIEeprom = ADDIDATA_EEPROM, + .pc_EepromChip = ADDIDATA_S5920, + .i_NbrAiChannelDiff = 8, + .i_AiChannelList = 8, + .i_AiMaxdata = 0x3ffff, + .pr_AiRangelist = &range_apci3300_ai, + .i_NbrDiChannel = 4, + .i_NbrDoChannel = 4, + .ui_MinAcquisitiontimeNs = 10000, + .ui_MinDelaytimeNs = 100000, + .interrupt = apci3200_interrupt, + .reset = apci3200_reset, + .ai_config = apci3200_ai_config, + .ai_read = apci3200_ai_read, + .ai_write = apci3200_ai_write, + .ai_bits = apci3200_ai_bits_test, + .ai_cmdtest = apci3200_ai_cmdtest, + .ai_cmd = apci3200_ai_cmd, + .ai_cancel = apci3200_cancel, + .di_bits = apci3200_di_insn_bits, + .do_bits = apci3200_do_insn_bits, + }, +}; + +static int apci3200_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + const struct addi_board *board = NULL; + + if (context < ARRAY_SIZE(apci3200_boardtypes)) + board = &apci3200_boardtypes[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + + return addi_auto_attach(dev, context); +} + +static struct comedi_driver apci3200_driver = { + .driver_name = "addi_apci_3200", + .module = THIS_MODULE, + .auto_attach = apci3200_auto_attach, + .detach = i_ADDI_Detach, +}; + +static int apci3200_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci3200_driver, id->driver_data); +} + +static const struct pci_device_id apci3200_pci_table[] = { + { PCI_VDEVICE(ADDIDATA, 0x3000), BOARD_APCI3200 }, + { PCI_VDEVICE(ADDIDATA, 0x3007), BOARD_APCI3300 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci3200_pci_table); + +static struct pci_driver apci3200_pci_driver = { + .name = "addi_apci_3200", + .id_table = apci3200_pci_table, + .probe = apci3200_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci3200_driver, apci3200_pci_driver); 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 00000000000..49bf1fb840f --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3501.c @@ -0,0 +1,447 @@ +/* + * 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/pci.h> +#include <linux/interrupt.h> +#include <linux/sched.h> + +#include "../comedidev.h" +#include "comedi_fc.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 val = 0; + int i; + int ret; + + /* + * 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 { + val |= APCI3501_AO_DATA_BIPOLAR; + outl(APCI3501_AO_CTRL_BIPOLAR, + dev->iobase + APCI3501_AO_CTRL_STATUS_REG); + } + + val |= APCI3501_AO_DATA_CHAN(chan); + + for (i = 0; i < insn->n; 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(val | APCI3501_AO_DATA_VAL(data[i]), + dev->iobase + APCI3501_AO_DATA_REG); + } + + 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; + int i_temp; + + /* 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)) { + comedi_error(dev, "IRQ from unknown source"); + 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); + i_temp = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1; + + 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_WRITEABLE | 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; + } 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_WRITEABLE; + 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_WRITEABLE | SDF_GROUND | SDF_COMMON; + 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); + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(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 00000000000..0532b6cc40e --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3xxx.c @@ -0,0 +1,982 @@ +/* + * 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/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.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 { + void __iomem *mmio; + 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 apci3xxx_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + unsigned int val; + + /* Test if interrupt occur */ + status = readl(devpriv->mmio + 16); + if ((status & 0x2) == 0x2) { + /* Reset the interrupt */ + writel(status, devpriv->mmio + 16); + + val = readl(devpriv->mmio + 28); + comedi_buf_put(s, val); + + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int apci3xxx_ai_started(struct comedi_device *dev) +{ + struct apci3xxx_private *devpriv = dev->private; + + if ((readl(devpriv->mmio + 8) & 0x80000) == 0x80000) + return 1; + else + return 0; + +} + +static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec) +{ + struct apci3xxx_private *devpriv = dev->private; + 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, devpriv->mmio + 12); + + /* Get and save the delay mode */ + delay_mode = readl(devpriv->mmio + 4); + delay_mode &= 0xfffffef0; + + /* Channel configuration selection */ + writel(delay_mode, devpriv->mmio + 4); + + /* Make the configuration */ + val = (range & 3) | ((range >> 2) << 6) | + ((aref == AREF_DIFF) << 7); + writel(val, devpriv->mmio + 0); + + /* Channel selection */ + writel(delay_mode | 0x100, devpriv->mmio + 4); + writel(chan, devpriv->mmio + 0); + + /* Restore delay mode */ + writel(delay_mode, devpriv->mmio + 4); + + /* Set the number of sequence to 1 */ + writel(1, devpriv->mmio + 48); + + return 0; +} + +static int apci3xxx_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + struct apci3xxx_private *devpriv = dev->private; + unsigned int status; + + status = readl(devpriv->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) +{ + struct apci3xxx_private *devpriv = dev->private; + 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, devpriv->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(devpriv->mmio + 28); + } + + return insn->n; +} + +static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev, + unsigned int *ns, int round_mode) +{ + const struct apci3xxx_boardinfo *board = comedi_board(dev); + 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 (round_mode) { + case TRIG_ROUND_NEAREST: + default: + timer = (*ns + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + timer = *ns / base; + break; + case TRIG_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 = comedi_board(dev); + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + board->ai_min_acq_ns); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_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, devpriv->mmio + 36); + + /* Set the convert timing */ + writel(devpriv->ai_timer, devpriv->mmio + 32); + + /* Start the conversion */ + writel(0x180000, devpriv->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) +{ + struct apci3xxx_private *devpriv = dev->private; + unsigned int status; + + status = readl(devpriv->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) +{ + struct apci3xxx_private *devpriv = dev->private; + 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++) { + /* Set the range selection */ + writel(range, devpriv->mmio + 96); + + /* Write the analog value to the selected channel */ + writel((data[i] << 8) | chan, devpriv->mmio + 100); + + /* Wait the end of transfer */ + ret = comedi_timeout(dev, s, insn, apci3xxx_ao_eoc, 0); + if (ret) + return ret; + } + + 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; + else + /* 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) +{ + struct apci3xxx_private *devpriv = dev->private; + unsigned int val; + int i; + + /* Disable the interrupt */ + disable_irq(dev->irq); + + /* Clear the start command */ + writel(0, devpriv->mmio + 8); + + /* Reset the interrupt flags */ + val = readl(devpriv->mmio + 16); + writel(val, devpriv->mmio + 16); + + /* clear the EOS */ + readl(devpriv->mmio + 20); + + /* Clear the FIFO */ + for (i = 0; i < 16; i++) + val = readl(devpriv->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); + devpriv->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_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->range_table = &apci3xxx_ao_range; + s->insn_write = apci3xxx_ao_insn_write; + + 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_WRITEABLE; + 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_WRITEABLE; + 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) +{ + struct apci3xxx_private *devpriv = dev->private; + + if (devpriv) { + if (dev->iobase) + apci3xxx_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv->mmio) + iounmap(devpriv->mmio); + } + comedi_pci_disable(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_watchdog.c b/drivers/staging/comedi/drivers/addi_watchdog.c new file mode 100644 index 00000000000..23031feaa09 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_watchdog.c @@ -0,0 +1,161 @@ +/* + * 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_watchdog.h" + +/* + * Register offsets/defines for the addi-data watchdog + */ +#define ADDI_WDOG_REG 0x00 +#define ADDI_WDOG_RELOAD_REG 0x04 +#define ADDI_WDOG_TIMEBASE 0x08 +#define ADDI_WDOG_CTRL_REG 0x0c +#define ADDI_WDOG_CTRL_ENABLE (1 << 0) +#define ADDI_WDOG_CTRL_SW_TRIG (1 << 9) +#define ADDI_WDOG_STATUS_REG 0x10 +#define ADDI_WDOG_STATUS_ENABLED (1 << 0) +#define ADDI_WDOG_STATUS_SW_TRIG (1 << 1) + +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_WDOG_CTRL_ENABLE; + reload = data[1] & s->maxdata; + outl(reload, spriv->iobase + ADDI_WDOG_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_WDOG_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_WDOG_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_WDOG_CTRL_SW_TRIG, + spriv->iobase + ADDI_WDOG_CTRL_REG); + } + + return insn->n; +} + +void addi_watchdog_reset(unsigned long iobase) +{ + outl(0x0, iobase + ADDI_WDOG_CTRL_REG); + outl(0x0, iobase + ADDI_WDOG_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_WRITEABLE; + 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 00000000000..83b47befa4d --- /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 00000000000..921f6942dfc --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci6208.c @@ -0,0 +1,260 @@ +/* + * 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] + * (ADLink) PCI-6216 [adl_pci6216] + * Author: nsyeow <nsyeow@pd.jaring.my> + * Updated: Fri, 30 Jan 2004 14:44:27 +0800 + * Status: untested + * + * Configuration Options: not applicable, uses PCI auto config + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pci.h> + +#include "../comedidev.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) + +#define PCI6208_MAX_AO_CHANNELS 16 + +enum pci6208_boardid { + BOARD_PCI6208, + BOARD_PCI6216, +}; + +struct pci6208_board { + const char *name; + int ao_chans; +}; + +static const struct pci6208_board pci6208_boards[] = { + [BOARD_PCI6208] = { + .name = "adl_pci6208", + .ao_chans = 8, + }, + [BOARD_PCI6216] = { + .name = "adl_pci6216", + .ao_chans = 16, + }, +}; + +struct pci6208_private { + unsigned int ao_readback[PCI6208_MAX_AO_CHANNELS]; +}; + +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) +{ + struct pci6208_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = devpriv->ao_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)); + } + devpriv->ao_readback[chan] = val; + + return insn->n; +} + +static int pci6208_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci6208_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct pci6208_board *boardinfo = NULL; + struct pci6208_private *devpriv; + struct comedi_subdevice *s; + unsigned int val; + int ret; + + if (context < ARRAY_SIZE(pci6208_boards)) + boardinfo = &pci6208_boards[context]; + if (!boardinfo) + return -ENODEV; + dev->board_ptr = boardinfo; + dev->board_name = boardinfo->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); + + 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 = boardinfo->ao_chans; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = pci6208_ao_insn_write; + s->insn_read = pci6208_ao_insn_read; + + 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_disable, +}; + +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_VDEVICE(ADLINK, 0x6208), BOARD_PCI6208 }, + { PCI_VDEVICE(ADLINK, 0x6216), BOARD_PCI6216 }, + { 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 00000000000..5e3cc77a8a0 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci7x3x.c @@ -0,0 +1,280 @@ +/* + * 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] - 16 input / 16 output + (ADLink) PCI-7233 [adl_pci7233] - 32 input + (ADLink) PCI-7234 [adl_pci7234] - 32 output + (ADLink) PCI-7432 [adl_pci7432] - 32 input / 32 output + (ADLink) PCI-7433 [adl_pci7433] - 64 input + (ADLink) PCI-7434 [adl_pci7434] - 64 output +Author: H Hartley Sweeten <hsweeten@visionengravers.com> +Updated: Thu, 02 Aug 2012 14:27:46 -0700 +Status: untested + +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 <linux/pci.h> + +#include "../comedidev.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); + + /* + * 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 + */ + 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_disable, +}; + +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 00000000000..300df55a280 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci8164.c @@ -0,0 +1,164 @@ +/* + * 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 <linux/pci.h> + +#include "../comedidev.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_disable, +}; + +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 00000000000..584fd57ecb7 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci9111.c @@ -0,0 +1,903 @@ +/* + +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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "8253.h" +#include "plx9052.h" +#include "comedi_fc.h" + +#define PCI9111_DRIVER_NAME "adl_pci9111" +#define PCI9111_HR_DEVICE_ID 0x9111 + +#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; + + int stop_counter; + + unsigned int scan_delay; + unsigned int chunk_counter; + unsigned int chunk_num_samples; + + int ao_readback; + + unsigned int div1; + unsigned int div2; + + 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); +} + +static void pci9111_timer_set(struct comedi_device *dev) +{ + struct pci9111_private_data *dev_private = dev->private; + unsigned long timer_base = dev->iobase + PCI9111_8254_BASE_REG; + + i8254_set_mode(timer_base, 1, 0, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_base, 1, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 1, 2, I8254_MODE2 | I8254_BINARY); + + udelay(1); + + i8254_write(timer_base, 1, 2, dev_private->div2); + i8254_write(timer_base, 1, 1, dev_private->div1); +} + +enum pci9111_trigger_sources { + software, + timer_pacer, + external +}; + +static void pci9111_trigger_source_set(struct comedi_device *dev, + enum pci9111_trigger_sources source) +{ + int flags; + + /* Read the current trigger mode control bits */ + flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); + /* Mask off the EITS and TPST bits */ + flags &= 0x9; + + switch (source) { + case software: + break; + + case timer_pacer: + flags |= PCI9111_AI_TRIG_CTRL_TPST; + break; + + case external: + flags |= PCI9111_AI_TRIG_CTRL_ETIS; + break; + } + + outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); +} + +static void pci9111_pretrigger_set(struct comedi_device *dev, bool pretrigger) +{ + int flags; + + /* Read the current trigger mode control bits */ + flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); + /* Mask off the PTRG bit */ + flags &= 0x7; + + if (pretrigger) + flags |= PCI9111_AI_TRIG_CTRL_PTRG; + + outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); +} + +static void pci9111_autoscan_set(struct comedi_device *dev, bool autoscan) +{ + int flags; + + /* Read the current trigger mode control bits */ + flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); + /* Mask off the ASCAN bit */ + flags &= 0xe; + + if (autoscan) + flags |= PCI9111_AI_TRIG_CTRL_ASCAN; + + outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); +} + +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); + + pci9111_trigger_source_set(dev, software); + + pci9111_autoscan_set(dev, false); + + 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) +{ + struct pci9111_private_data *dev_private = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, + TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + PCI9111_AI_ACQUISITION_PERIOD_MIN_NS); + else /* TRIG_EXT */ + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + PCI9111_AI_ACQUISITION_PERIOD_MIN_NS); + else /* TRIG_FOLLOW || TRIG_EXT */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ, + &dev_private->div1, + &dev_private->div2, + &arg, cmd->flags); + err |= cfc_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 |= cfc_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; + + /* 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) { + outb(cmd->chanlist_len - 1, + dev->iobase + PCI9111_AI_CHANNEL_REG); + pci9111_autoscan_set(dev, true); + } else { + outb(CR_CHAN(cmd->chanlist[0]), + dev->iobase + PCI9111_AI_CHANNEL_REG); + pci9111_autoscan_set(dev, false); + } + + /* 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 counter */ + if (cmd->stop_src == TRIG_COUNT) + dev_private->stop_counter = cmd->stop_arg * cmd->chanlist_len; + else /* TRIG_NONE */ + dev_private->stop_counter = 0; + + /* Set timer pacer */ + dev_private->scan_delay = 0; + if (cmd->convert_src == TRIG_TIMER) { + pci9111_trigger_source_set(dev, software); + pci9111_timer_set(dev); + pci9111_fifo_reset(dev); + pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, + irq_on_timer_tick); + pci9111_trigger_source_set(dev, timer_pacer); + 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 */ + pci9111_trigger_source_set(dev, external); + 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); + + } + + dev_private->stop_counter *= (1 + dev_private->scan_delay); + 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 = num_bytes / sizeof(short); + 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 total = 0; + unsigned int samples; + + if (cmd->stop_src == TRIG_COUNT && + PCI9111_FIFO_HALF_SIZE > devpriv->stop_counter) + samples = devpriv->stop_counter; + else + samples = PCI9111_FIFO_HALF_SIZE; + + insw(dev->iobase + PCI9111_AI_FIFO_REG, + devpriv->ai_bounce_buffer, samples); + + if (devpriv->scan_delay < 1) { + total = cfc_write_array_to_buffer(s, + devpriv->ai_bounce_buffer, + samples * sizeof(short)); + } 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; + + total += cfc_write_array_to_buffer(s, + devpriv->ai_bounce_buffer + pos, + to_read * sizeof(short)); + } else { + to_read = devpriv->chunk_num_samples - + devpriv->chunk_counter; + + if (to_read > samples - pos) + to_read = samples - pos; + + total += to_read * sizeof(short); + } + + pos += to_read; + devpriv->chunk_counter += to_read; + + if (devpriv->chunk_counter >= + devpriv->chunk_num_samples) + devpriv->chunk_counter = 0; + } + } + + devpriv->stop_counter -= total / sizeof(short); +} + +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); + comedi_error(dev, PCI9111_DRIVER_NAME " fifo overflow"); + outb(0, dev->iobase + PCI9111_INT_CLR_REG); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_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 && dev_private->stop_counter == 0) + async->events |= COMEDI_CB_EOA; + + outb(0, dev->iobase + PCI9111_INT_CLR_REG); + + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + cfc_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) +{ + struct pci9111_private_data *dev_private = dev->private; + unsigned int val = 0; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, dev->iobase + PCI9111_AO_REG); + } + dev_private->ao_readback = val; + + return insn->n; +} + +static int pci9111_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci9111_private_data *dev_private = dev->private; + int i; + + for (i = 0; i < insn->n; i++) + data[i] = dev_private->ao_readback; + + 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); + + pci9111_trigger_source_set(dev, software); + pci9111_pretrigger_set(dev, false); + pci9111_autoscan_set(dev, false); + + /* Reset 8254 chip */ + dev_private->div1 = 0; + dev_private->div2 = 0; + pci9111_timer_set(dev); + + 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; + } + + 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; + s->insn_read = pci9111_ao_insn_read; + + 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_READABLE | 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); + if (dev->irq != 0) + free_irq(dev->irq, dev); + comedi_pci_disable(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, PCI9111_HR_DEVICE_ID) }, + /* { 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 00000000000..59a65cbc6db --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci9118.c @@ -0,0 +1,2090 @@ +/* + * 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 cann't be used if cmd->scan_begin_src=TRIG_FOLLOW + and is used flag TRIG_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] - 0=autoselect DMA or EOC interrupts operation + 1 = disable DMA mode + 3 = disable DMA and INT, only insn interface will work + [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] - 0=stop measure on all hardware errors + 2 | = ignore ADOR - A/D Overrun status + 8|=ignore Bover - A/D Burst Mode Overrun status + 256|=ignore nFull - A/D FIFO Full status + +*/ + +/* + * 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/pci.h> +#include <linux/delay.h> +#include <linux/gfp.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include "../comedidev.h" + +#include "amcc_s5933.h" +#include "8253.h" +#include "comedi_fc.h" + +/* paranoid checks are broken */ +#undef PCI9118_PARANOIDCHECK /* + * if defined, then is used code which control + * correct channel number on every 12 bit sample + */ + +#define IORANGE_9118 64 /* I hope */ +#define PCI9118_CHANLEN 255 /* + * len of chanlist, some source say 256, + * but reality looks like 255 :-( + */ + +#define PCI9118_CNT0 0x00 /* R/W: 8254 counter 0 */ +#define PCI9118_CNT1 0x04 /* R/W: 8254 counter 0 */ +#define PCI9118_CNT2 0x08 /* R/W: 8254 counter 0 */ +#define PCI9118_CNTCTRL 0x0c /* W: 8254 counter control */ +#define PCI9118_AD_DATA 0x10 /* R: A/D data */ +#define PCI9118_DA1 0x10 /* W: D/A registers */ +#define PCI9118_DA2 0x14 +#define PCI9118_ADSTAT 0x18 /* R: A/D status register */ +#define PCI9118_ADCNTRL 0x18 /* W: A/D control register */ +#define PCI9118_DI 0x1c /* R: digi input register */ +#define PCI9118_DO 0x1c /* W: digi output register */ +#define PCI9118_SOFTTRG 0x20 /* W: soft trigger for A/D */ +#define PCI9118_GAIN 0x24 /* W: A/D gain/channel register */ +#define PCI9118_BURST 0x28 /* W: A/D burst number register */ +#define PCI9118_SCANMOD 0x2c /* W: A/D auto scan mode */ +#define PCI9118_ADFUNC 0x30 /* W: A/D function register */ +#define PCI9118_DELFIFO 0x34 /* W: A/D data FIFO reset */ +#define PCI9118_INTSRC 0x38 /* R: interrupt reason register */ +#define PCI9118_INTCTRL 0x38 /* W: interrupt control register */ + +/* bits from A/D control register (PCI9118_ADCNTRL) */ +#define AdControl_UniP 0x80 /* 1=bipolar, 0=unipolar */ +#define AdControl_Diff 0x40 /* 1=differential, 0= single end inputs */ +#define AdControl_SoftG 0x20 /* 1=8254 counter works, 0=counter stops */ +#define AdControl_ExtG 0x10 /* + * 1=8254 countrol controlled by TGIN(pin 46), + * 0=controlled by SoftG + */ +#define AdControl_ExtM 0x08 /* + * 1=external hardware trigger (pin 44), + * 0=internal trigger + */ +#define AdControl_TmrTr 0x04 /* + * 1=8254 is iternal trigger source, + * 0=software trigger is source + * (register PCI9118_SOFTTRG) + */ +#define AdControl_Int 0x02 /* 1=enable INT, 0=disable */ +#define AdControl_Dma 0x01 /* 1=enable DMA, 0=disable */ + +/* bits from A/D function register (PCI9118_ADFUNC) */ +#define AdFunction_PDTrg 0x80 /* + * 1=positive, + * 0=negative digital trigger + * (only positive is correct) + */ +#define AdFunction_PETrg 0x40 /* + * 1=positive, + * 0=negative external trigger + * (only positive is correct) + */ +#define AdFunction_BSSH 0x20 /* 1=with sample&hold, 0=without */ +#define AdFunction_BM 0x10 /* 1=burst mode, 0=normal mode */ +#define AdFunction_BS 0x08 /* + * 1=burst mode start, + * 0=burst mode stop + */ +#define AdFunction_PM 0x04 /* + * 1=post trigger mode, + * 0=not post trigger + */ +#define AdFunction_AM 0x02 /* + * 1=about trigger mode, + * 0=not about trigger + */ +#define AdFunction_Start 0x01 /* 1=trigger start, 0=trigger stop */ + +/* bits from A/D status register (PCI9118_ADSTAT) */ +#define AdStatus_nFull 0x100 /* 0=FIFO full (fatal), 1=not full */ +#define AdStatus_nHfull 0x080 /* 0=FIFO half full, 1=FIFO not half full */ +#define AdStatus_nEpty 0x040 /* 0=FIFO empty, 1=FIFO not empty */ +#define AdStatus_Acmp 0x020 /* */ +#define AdStatus_DTH 0x010 /* 1=external digital trigger */ +#define AdStatus_Bover 0x008 /* 1=burst mode overrun (fatal) */ +#define AdStatus_ADOS 0x004 /* 1=A/D over speed (warning) */ +#define AdStatus_ADOR 0x002 /* 1=A/D overrun (fatal) */ +#define AdStatus_ADrdy 0x001 /* 1=A/D already ready, 0=not ready */ + +/* bits for interrupt reason and control (PCI9118_INTSRC, PCI9118_INTCTRL) */ +/* 1=interrupt occur, enable source, 0=interrupt not occur, disable source */ +#define Int_Timer 0x08 /* timer interrupt */ +#define Int_About 0x04 /* about trigger complete */ +#define Int_Hfull 0x02 /* A/D FIFO hlaf full */ +#define Int_DTrg 0x01 /* external digital trigger */ + +#define START_AI_EXT 0x01 /* start measure on external trigger */ +#define STOP_AI_EXT 0x02 /* stop measure on external trigger */ +#define START_AI_INT 0x04 /* start measure on internal trigger */ +#define STOP_AI_INT 0x08 /* stop measure on internal trigger */ + +#define EXTTRG_AI 0 /* ext trg is used by AI */ + +static const struct comedi_lrange range_pci9118dg_hr = { + 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 range_pci9118hg = { + 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 + */ + +struct boardtype { + const char *name; /* board name */ + int device_id; /* PCI device ID of card */ + int iorange_amcc; /* iorange for own S5933 region */ + int iorange_9118; /* pass thru card region size */ + int n_aichan; /* num of A/D chans */ + int n_aichand; /* num of A/D chans in diff mode */ + int mux_aichan; /* + * num of A/D chans with + * external multiplexor + */ + int n_aichanlist; /* len of chanlist */ + int n_aochan; /* num of D/A chans */ + int ai_maxdata; /* resolution of A/D */ + int ao_maxdata; /* resolution of D/A */ + const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */ + const struct comedi_lrange *rangelist_ao; /* rangelist for D/A */ + unsigned int ai_ns_min; /* max sample speed of card v ns */ + unsigned int ai_pacer_min; /* + * minimal pacer value + * (c1*c2 or c1 in burst) + */ + int half_fifo_size; /* size of FIFO/2 */ + +}; + +static const struct boardtype boardtypes[] = { + { + .name = "pci9118dg", + .device_id = 0x80d9, + .iorange_amcc = AMCC_OP_REG_SIZE, + .iorange_9118 = IORANGE_9118, + .n_aichan = 16, + .n_aichand = 8, + .mux_aichan = 256, + .n_aichanlist = PCI9118_CHANLEN, + .n_aochan = 2, + .ai_maxdata = 0x0fff, + .ao_maxdata = 0x0fff, + .rangelist_ai = &range_pci9118dg_hr, + .rangelist_ao = &range_bipolar10, + .ai_ns_min = 3000, + .ai_pacer_min = 12, + .half_fifo_size = 512, + }, { + .name = "pci9118hg", + .device_id = 0x80d9, + .iorange_amcc = AMCC_OP_REG_SIZE, + .iorange_9118 = IORANGE_9118, + .n_aichan = 16, + .n_aichand = 8, + .mux_aichan = 256, + .n_aichanlist = PCI9118_CHANLEN, + .n_aochan = 2, + .ai_maxdata = 0x0fff, + .ao_maxdata = 0x0fff, + .rangelist_ai = &range_pci9118hg, + .rangelist_ao = &range_bipolar10, + .ai_ns_min = 3000, + .ai_pacer_min = 12, + .half_fifo_size = 512, + }, { + .name = "pci9118hr", + .device_id = 0x80d9, + .iorange_amcc = AMCC_OP_REG_SIZE, + .iorange_9118 = IORANGE_9118, + .n_aichan = 16, + .n_aichand = 8, + .mux_aichan = 256, + .n_aichanlist = PCI9118_CHANLEN, + .n_aochan = 2, + .ai_maxdata = 0xffff, + .ao_maxdata = 0x0fff, + .rangelist_ai = &range_pci9118dg_hr, + .rangelist_ao = &range_bipolar10, + .ai_ns_min = 10000, + .ai_pacer_min = 40, + .half_fifo_size = 512, + }, +}; + +struct pci9118_private { + unsigned long iobase_a; /* base+size for AMCC chip */ + unsigned int master; /* master capable */ + unsigned int usemux; /* we want to use external multiplexor! */ +#ifdef PCI9118_PARANOIDCHECK + unsigned short chanlist[PCI9118_CHANLEN + 1]; /* + * list of + * scanned channel + */ + unsigned char chanlistlen; /* number of scanlist */ +#endif + unsigned char AdControlReg; /* A/D control register */ + unsigned char IntControlReg; /* Interrupt control register */ + unsigned char AdFunctionReg; /* A/D function register */ + char ai_neverending; /* we do unlimited AI */ + unsigned int ai_do; /* what do AI? 0=nothing, 1 to 4 mode */ + unsigned int ai_act_scan; /* how many scans we finished */ + 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 ai_divisor1, ai_divisor2; /* + * divisors for start of measure + * on external start + */ + unsigned short ao_data[2]; /* data output buffer */ + char dma_doublebuf; /* we can use double buffering */ + unsigned int dma_actbuf; /* which buffer is used now */ + unsigned short *dmabuf_virt[2]; /* + * pointers to begin of + * DMA buffer + */ + unsigned long dmabuf_hw[2]; /* hw address of DMA buff */ + unsigned int dmabuf_size[2]; /* + * size of dma buffer in bytes + */ + unsigned int dmabuf_use_size[2]; /* + * which size we may now use + * for transfer + */ + unsigned int dmabuf_used_size[2]; /* which size was truly used */ + unsigned int dmabuf_panic_size[2]; + int dmabuf_pages[2]; /* number of pages in buffer */ + unsigned char exttrg_users; /* + * bit field of external trigger + * users(0-AI, 1-AO, 2-DI, 3-DO) + */ + unsigned char usedma; /* =1 use DMA transfer and not INT */ + 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_maskerr; /* which warning was printed */ + unsigned int ai_maskharderr; /* on which error bits stops */ +}; + +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, int n_chan, + unsigned int *chanlist, int frontadd, int backadd) +{ + const struct boardtype *this_board = comedi_board(dev); + 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) { + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + if ((frontadd + n_chan + backadd) > s->len_chanlist) { + comedi_error(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)) { + comedi_error(dev, + "Differencial and single ended " + "inputs can't be mixtured!"); + return 0; + } + if ((CR_RANGE(chanlist[i]) < PCI9118_BIPOLAR_RANGES) != + (bipolar)) { + comedi_error(dev, + "Bipolar and unipolar ranges " + "can't be mixtured!"); + return 0; + } + if (!devpriv->usemux && differencial && + (CR_CHAN(chanlist[i]) >= this_board->n_aichand)) { + comedi_error(dev, + "If AREF_DIFF is used then is " + "available only first 8 channels!"); + return 0; + } + } + + return 1; +} + +static int setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, int n_chan, + unsigned int *chanlist, int rot, int frontadd, + int backadd, int usedma) +{ + struct pci9118_private *devpriv = dev->private; + unsigned int i, differencial = 0, bipolar = 0; + unsigned int scanquad, gain, ssh = 0x00; + + if (usedma == 1) { + rot = 8; + usedma = 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 */ + + /* All is ok, so we can setup channel/range list */ + + if (!bipolar) { + devpriv->AdControlReg |= AdControl_UniP; + /* set unibipolar */ + } else { + devpriv->AdControlReg &= ((~AdControl_UniP) & 0xff); + /* enable bipolar */ + } + + if (differencial) { + devpriv->AdControlReg |= AdControl_Diff; + /* enable diff inputs */ + } else { + devpriv->AdControlReg &= ((~AdControl_Diff) & 0xff); + /* set single ended inputs */ + } + + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* setup mode */ + + outl(2, dev->iobase + PCI9118_SCANMOD); + /* gods know why this sequence! */ + outl(0, dev->iobase + PCI9118_SCANMOD); + outl(1, dev->iobase + PCI9118_SCANMOD); + +#ifdef PCI9118_PARANOIDCHECK + devpriv->chanlistlen = n_chan; + for (i = 0; i < (PCI9118_CHANLEN + 1); i++) + devpriv->chanlist[i] = 0x55aa; +#endif + + if (frontadd) { /* insert channels for S&H */ + ssh = devpriv->softsshsample; + for (i = 0; i < frontadd; i++) { + /* store range list to card */ + scanquad = CR_CHAN(chanlist[0]); + /* get channel number; */ + gain = CR_RANGE(chanlist[0]); + /* get gain number */ + scanquad |= ((gain & 0x03) << 8); + outl(scanquad | ssh, dev->iobase + PCI9118_GAIN); + ssh = devpriv->softsshhold; + } + } + + for (i = 0; i < n_chan; i++) { /* store range list to card */ + scanquad = CR_CHAN(chanlist[i]); /* get channel number */ +#ifdef PCI9118_PARANOIDCHECK + devpriv->chanlist[i ^ usedma] = (scanquad & 0xf) << rot; +#endif + gain = CR_RANGE(chanlist[i]); /* get gain number */ + scanquad |= ((gain & 0x03) << 8); + outl(scanquad | ssh, dev->iobase + PCI9118_GAIN); + } + + if (backadd) { /* insert channels for fit onto 32bit DMA */ + for (i = 0; i < backadd; i++) { /* store range list to card */ + scanquad = CR_CHAN(chanlist[0]); + /* get channel number */ + gain = CR_RANGE(chanlist[0]); /* get gain number */ + scanquad |= ((gain & 0x03) << 8); + outl(scanquad | ssh, dev->iobase + PCI9118_GAIN); + } + } +#ifdef PCI9118_PARANOIDCHECK + devpriv->chanlist[n_chan ^ usedma] = devpriv->chanlist[0 ^ usedma]; + /* for 32bit operations */ +#endif + outl(0, dev->iobase + PCI9118_SCANMOD); /* close scan queue */ + /* udelay(100); important delay, or first sample will be crippled */ + + return 1; /* we can serve this with scan logic */ +} + +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_ADSTAT); + if (status & AdStatus_ADrdy) + return 0; + return -EBUSY; +} + +static int pci9118_insn_read_ai(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci9118_private *devpriv = dev->private; + int ret; + int n; + + devpriv->AdControlReg = AdControl_Int & 0xff; + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + /* + * positive triggers, no S&H, + * no burst, burst stop, + * no post trigger, + * no about trigger, + * trigger stop + */ + + if (!setup_channel_list(dev, s, 1, &insn->chanspec, 0, 0, 0, 0)) + return -EINVAL; + + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + + for (n = 0; n < insn->n; n++) { + outw(0, dev->iobase + PCI9118_SOFTTRG); /* start conversion */ + udelay(2); + + ret = comedi_timeout(dev, s, insn, pci9118_ai_eoc, 0); + if (ret) { + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + return ret; + } + + if (s->maxdata == 0xffff) { + data[n] = + (inl(dev->iobase + + PCI9118_AD_DATA) & 0xffff) ^ 0x8000; + } else { + data[n] = + (inw(dev->iobase + PCI9118_AD_DATA) >> 4) & 0xfff; + } + } + + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + return n; + +} + +static int pci9118_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci9118_private *devpriv = dev->private; + int n, chanreg, ch; + + ch = CR_CHAN(insn->chanspec); + if (ch) + chanreg = PCI9118_DA2; + else + chanreg = PCI9118_DA1; + + + for (n = 0; n < insn->n; n++) { + outl(data[n], dev->iobase + chanreg); + devpriv->ao_data[ch] = data[n]; + } + + return n; +} + +static int pci9118_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci9118_private *devpriv = dev->private; + int n, chan; + + chan = CR_CHAN(insn->chanspec); + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +static int pci9118_insn_bits_di(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = inl(dev->iobase + PCI9118_DI) & 0xf; + + return insn->n; +} + +static int pci9118_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)) + outl(s->state & 0x0f, dev->iobase + PCI9118_DO); + + data[1] = s->state; + + return insn->n; +} + +static void interrupt_pci9118_ai_mode4_switch(struct comedi_device *dev) +{ + struct pci9118_private *devpriv = dev->private; + + devpriv->AdFunctionReg = + AdFunction_PDTrg | AdFunction_PETrg | AdFunction_AM; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + outl(0x30, dev->iobase + PCI9118_CNTCTRL); + outl((devpriv->dmabuf_hw[1 - devpriv->dma_actbuf] >> 1) & 0xff, + dev->iobase + PCI9118_CNT0); + outl((devpriv->dmabuf_hw[1 - devpriv->dma_actbuf] >> 9) & 0xff, + dev->iobase + PCI9118_CNT0); + devpriv->AdFunctionReg |= AdFunction_Start; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); +} + +static unsigned int defragment_dma_buffer(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *dma_buffer, + unsigned int num_samples) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int i = 0, j = 0; + unsigned int start_pos = devpriv->ai_add_front, + stop_pos = devpriv->ai_add_front + cmd->chanlist_len; + unsigned int raw_scanlen = devpriv->ai_add_front + cmd->chanlist_len + + devpriv->ai_add_back; + + for (i = 0; i < num_samples; i++) { + if (devpriv->ai_act_dmapos >= start_pos && + devpriv->ai_act_dmapos < stop_pos) { + dma_buffer[j++] = dma_buffer[i]; + } + devpriv->ai_act_dmapos++; + devpriv->ai_act_dmapos %= raw_scanlen; + } + + return j; +} + +static int move_block_from_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *dma_buffer, + unsigned int num_samples) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int num_bytes; + + num_samples = defragment_dma_buffer(dev, s, dma_buffer, num_samples); + devpriv->ai_act_scan += + (s->async->cur_chan + num_samples) / cmd->scan_end_arg; + s->async->cur_chan += num_samples; + s->async->cur_chan %= cmd->scan_end_arg; + num_bytes = + cfc_write_array_to_buffer(s, dma_buffer, + num_samples * sizeof(short)); + if (num_bytes < num_samples * sizeof(short)) + return -1; + return 0; +} + +static int pci9118_exttrg_add(struct comedi_device *dev, unsigned char source) +{ + struct pci9118_private *devpriv = dev->private; + + if (source > 3) + return -1; /* incorrect source */ + devpriv->exttrg_users |= (1 << source); + devpriv->IntControlReg |= Int_DTrg; + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | 0x1f00, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow INT in AMCC */ + return 0; +} + +static int pci9118_exttrg_del(struct comedi_device *dev, unsigned char source) +{ + struct pci9118_private *devpriv = dev->private; + + if (source > 3) + return -1; /* incorrect source */ + devpriv->exttrg_users &= ~(1 << source); + if (!devpriv->exttrg_users) { /* shutdown ext trg intterrupts */ + devpriv->IntControlReg &= ~Int_DTrg; + if (!devpriv->IntControlReg) /* all IRQ disabled */ + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) & + (~0x00001f00), + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* disable int in AMCC */ + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + } + return 0; +} + +static void pci9118_calc_divisors(char mode, 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) +{ + const struct boardtype *this_board = comedi_board(dev); + struct comedi_cmd *cmd = &s->async->cmd; + + switch (mode) { + case 1: + case 4: + if (*tim2 < this_board->ai_ns_min) + *tim2 = this_board->ai_ns_min; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_4MHZ, + div1, div2, + tim2, flags & TRIG_ROUND_NEAREST); + break; + case 2: + if (*tim2 < this_board->ai_ns_min) + *tim2 = this_board->ai_ns_min; + *div1 = *tim2 / I8254_OSC_BASE_4MHZ; + /* convert timer (burst) */ + if (*div1 < this_board->ai_pacer_min) + *div1 = this_board->ai_pacer_min; + *div2 = *tim1 / I8254_OSC_BASE_4MHZ; /* scan timer */ + *div2 = *div2 / *div1; /* major timer is c1*c2 */ + if (*div2 < chans) + *div2 = chans; + + *tim2 = *div1 * I8254_OSC_BASE_4MHZ; /* real convert timer */ + + if (cmd->convert_src == TRIG_NOW && !chnsshfront) { + /* use BSSH signal */ + if (*div2 < (chans + 2)) + *div2 = chans + 2; + } + + *tim1 = *div1 * *div2 * I8254_OSC_BASE_4MHZ; + break; + } +} + +static void pci9118_start_pacer(struct comedi_device *dev, int mode) +{ + struct pci9118_private *devpriv = dev->private; + unsigned int divisor1 = devpriv->ai_divisor1; + unsigned int divisor2 = devpriv->ai_divisor2; + + outl(0x74, dev->iobase + PCI9118_CNTCTRL); + outl(0xb4, dev->iobase + PCI9118_CNTCTRL); +/* outl(0x30, dev->iobase + PCI9118_CNTCTRL); */ + udelay(1); + + if ((mode == 1) || (mode == 2) || (mode == 4)) { + outl(divisor2 & 0xff, dev->iobase + PCI9118_CNT2); + outl((divisor2 >> 8) & 0xff, dev->iobase + PCI9118_CNT2); + outl(divisor1 & 0xff, dev->iobase + PCI9118_CNT1); + outl((divisor1 >> 8) & 0xff, dev->iobase + PCI9118_CNT1); + } +} + +static int pci9118_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + + if (devpriv->usedma) + outl(inl(devpriv->iobase_a + AMCC_OP_REG_MCSR) & + (~EN_A2P_TRANSFERS), + devpriv->iobase_a + AMCC_OP_REG_MCSR); /* stop DMA */ + pci9118_exttrg_del(dev, EXTTRG_AI); + pci9118_start_pacer(dev, 0); /* stop 8254 counters */ + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + /* + * positive triggers, no S&H, no burst, + * burst stop, no post trigger, + * no about trigger, trigger stop + */ + devpriv->AdControlReg = 0x00; + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* + * bipolar, S.E., use 8254, stop 8354, + * internal trigger, soft trigger, + * disable INT and DMA + */ + outl(0, dev->iobase + PCI9118_BURST); + outl(1, dev->iobase + PCI9118_SCANMOD); + outl(2, dev->iobase + PCI9118_SCANMOD); /* reset scan queue */ + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + + devpriv->ai_do = 0; + devpriv->usedma = 0; + + devpriv->ai_act_scan = 0; + devpriv->ai_act_dmapos = 0; + s->async->cur_chan = 0; + s->async->inttrig = NULL; + devpriv->ai_neverending = 0; + devpriv->dma_actbuf = 0; + + if (!devpriv->IntControlReg) + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | 0x1f00, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow INT in AMCC */ + + return 0; +} + +static char pci9118_decode_error_status(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char m) +{ + struct pci9118_private *devpriv = dev->private; + + if (m & 0x100) { + comedi_error(dev, "A/D FIFO Full status (Fatal Error!)"); + devpriv->ai_maskerr &= ~0x100L; + } + if (m & 0x008) { + comedi_error(dev, + "A/D Burst Mode Overrun Status (Fatal Error!)"); + devpriv->ai_maskerr &= ~0x008L; + } + if (m & 0x004) { + comedi_error(dev, "A/D Over Speed Status (Warning!)"); + devpriv->ai_maskerr &= ~0x004L; + } + if (m & 0x002) { + comedi_error(dev, "A/D Overrun Status (Fatal Error!)"); + devpriv->ai_maskerr &= ~0x002L; + } + if (m & devpriv->ai_maskharderr) { + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + return 1; + } + + 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 int i, num_samples = num_bytes / sizeof(short); + unsigned short *array = data; + + 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, + unsigned short int_adstat, + unsigned int int_amcc, + unsigned short int_daq) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short sampl; + + if (int_adstat & devpriv->ai_maskerr) + if (pci9118_decode_error_status(dev, s, int_adstat)) + return; + + sampl = inw(dev->iobase + PCI9118_AD_DATA); + +#ifdef PCI9118_PARANOIDCHECK + if (s->maxdata != 0xffff) { + if ((sampl & 0x000f) != devpriv->chanlist[s->async->cur_chan]) { + /* data dropout! */ + dev_info(dev->class_dev, + "A/D SAMPL - data dropout: received channel %d, expected %d!\n", + sampl & 0x000f, + devpriv->chanlist[s->async->cur_chan]); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + return; + } + } +#endif + cfc_write_to_buffer(s, sampl); + s->async->cur_chan++; + if (s->async->cur_chan >= cmd->scan_end_arg) { + /* one scan done */ + s->async->cur_chan %= cmd->scan_end_arg; + devpriv->ai_act_scan++; + if (!devpriv->ai_neverending) { + /* all data sampled? */ + if (devpriv->ai_act_scan >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + } + } + + cfc_handle_events(dev, s); +} + +static void interrupt_pci9118_ai_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short int_adstat, + unsigned int int_amcc, + unsigned short int_daq) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int next_dma_buf, samplesinbuf, sampls, m; + + if (int_amcc & MASTER_ABORT_INT) { + comedi_error(dev, "AMCC IRQ - MASTER DMA ABORT!"); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + return; + } + + if (int_amcc & TARGET_ABORT_INT) { + comedi_error(dev, "AMCC IRQ - TARGET DMA ABORT!"); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + return; + } + if (int_adstat & devpriv->ai_maskerr) + /* if (int_adstat & 0x106) */ + if (pci9118_decode_error_status(dev, s, int_adstat)) + return; + + samplesinbuf = devpriv->dmabuf_use_size[devpriv->dma_actbuf] >> 1; + /* number of received real samples */ + + if (devpriv->dma_doublebuf) { /* + * switch DMA buffers if is used + * double buffering + */ + next_dma_buf = 1 - devpriv->dma_actbuf; + outl(devpriv->dmabuf_hw[next_dma_buf], + devpriv->iobase_a + AMCC_OP_REG_MWAR); + outl(devpriv->dmabuf_use_size[next_dma_buf], + devpriv->iobase_a + AMCC_OP_REG_MWTC); + devpriv->dmabuf_used_size[next_dma_buf] = + devpriv->dmabuf_use_size[next_dma_buf]; + if (devpriv->ai_do == 4) + interrupt_pci9118_ai_mode4_switch(dev); + } + + if (samplesinbuf) { + /* how many samples is to end of buffer */ + m = s->async->prealloc_bufsz >> 1; + sampls = m; + move_block_from_dma(dev, s, + devpriv->dmabuf_virt[devpriv->dma_actbuf], + samplesinbuf); + m = m - sampls; /* m=how many samples was transferred */ + } + + if (!devpriv->ai_neverending) { + /* all data sampled? */ + if (devpriv->ai_act_scan >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + } + + if (devpriv->dma_doublebuf) { /* switch dma buffers */ + devpriv->dma_actbuf = 1 - devpriv->dma_actbuf; + } else { /* restart DMA if is not used double buffering */ + outl(devpriv->dmabuf_hw[0], + devpriv->iobase_a + AMCC_OP_REG_MWAR); + outl(devpriv->dmabuf_use_size[0], + devpriv->iobase_a + AMCC_OP_REG_MWTC); + if (devpriv->ai_do == 4) + interrupt_pci9118_ai_mode4_switch(dev); + } + + cfc_handle_events(dev, s); +} + +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_INTSRC) & 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); + + adstat = inw(dev->iobase + PCI9118_ADSTAT) & 0x1ff; + + if (!devpriv->ai_do) + return IRQ_HANDLED; + + if (devpriv->ai12_startstop) { + if ((adstat & AdStatus_DTH) && (intsrc & Int_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_del(dev, EXTTRG_AI); + + /* start pacer */ + pci9118_start_pacer(dev, devpriv->ai_do); + outl(devpriv->AdControlReg, + dev->iobase + PCI9118_ADCNTRL); + } else if (devpriv->ai12_startstop & STOP_AI_EXT) { + /* deactivate EXT trigger */ + devpriv->ai12_startstop &= ~STOP_AI_EXT; + pci9118_exttrg_del(dev, EXTTRG_AI); + + /* on next interrupt measure will stop */ + devpriv->ai_neverending = 0; + } + } + } + + if (devpriv->usedma) + interrupt_pci9118_ai_dma(dev, s, adstat, intcsr, intsrc); + else + interrupt_pci9118_ai_onesample(dev, s, adstat, intcsr, intsrc); + + return IRQ_HANDLED; +} + +static int pci9118_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + devpriv->ai12_startstop &= ~START_AI_INT; + s->async->inttrig = NULL; + + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + if (devpriv->ai_do != 3) { + pci9118_start_pacer(dev, devpriv->ai_do); + devpriv->AdControlReg |= AdControl_SoftG; + } + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + + return 1; +} + +static int pci9118_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci9118_private *devpriv = dev->private; + int err = 0; + unsigned int flags; + unsigned int arg; + unsigned int divisor1 = 0, divisor2 = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_EXT | TRIG_INT); + + flags = TRIG_FOLLOW; + if (devpriv->master) + flags |= TRIG_TIMER | TRIG_EXT; + err |= cfc_check_trigger_src(&cmd->scan_begin_src, flags); + + flags = TRIG_TIMER | TRIG_EXT; + if (devpriv->master) + flags |= TRIG_NOW; + err |= cfc_check_trigger_src(&cmd->convert_src, flags); + + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + this_board->ai_ns_min); + + if (cmd->scan_begin_src == TRIG_EXT) + if (cmd->scan_begin_arg) { + cmd->scan_begin_arg = 0; + err |= -EINVAL; + err |= cfc_check_trigger_arg_max(&cmd->scan_end_arg, + 65535); + } + + if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + this_board->ai_ns_min); + + if (cmd->convert_src == TRIG_EXT) + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_4MHZ, + &divisor1, &divisor2, + &arg, cmd->flags); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) { + arg = cmd->convert_arg; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_4MHZ, + &divisor1, &divisor2, + &arg, cmd->flags); + err |= cfc_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 = this_board->ai_ns_min * + (cmd->scan_end_arg + 2); + } else { + arg = cmd->convert_arg * cmd->chanlist_len; + } + err |= cfc_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 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; + unsigned int dmalen0, dmalen1, i; + + dmalen0 = devpriv->dmabuf_size[0]; + dmalen1 = devpriv->dmabuf_size[1]; + /* 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 & TRIG_WAKE_EOS) { + if (dmalen0 < (devpriv->ai_n_realscanlen << 1)) { + /* uff, too short DMA buffer, disable EOS support! */ + devpriv->ai_flags &= (~TRIG_WAKE_EOS); + dev_info(dev->class_dev, + "WAR: DMA0 buf too short, can't support TRIG_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 & TRIG_WAKE_EOS) { + if (dmalen1 < (devpriv->ai_n_realscanlen << 1)) { + /* uff, too short DMA buffer, disable EOS support! */ + devpriv->ai_flags &= (~TRIG_WAKE_EOS); + dev_info(dev->class_dev, + "WAR: DMA1 buf too short, can't support TRIG_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 TRIG_WAKE_EOS */ + if (!(devpriv->ai_flags & TRIG_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; + devpriv->dmabuf_use_size[0] = dmalen0; + devpriv->dmabuf_use_size[1] = dmalen1; + +#if 0 + if (cmd->scan_end_arg < this_board->half_fifo_size) { + devpriv->dmabuf_panic_size[0] = + (this_board->half_fifo_size / cmd->scan_end_arg + + 1) * cmd->scan_end_arg * sizeof(short); + devpriv->dmabuf_panic_size[1] = + (this_board->half_fifo_size / cmd->scan_end_arg + + 1) * cmd->scan_end_arg * sizeof(short); + } else { + devpriv->dmabuf_panic_size[0] = + (cmd->scan_end_arg << 1) % devpriv->dmabuf_size[0]; + devpriv->dmabuf_panic_size[1] = + (cmd->scan_end_arg << 1) % devpriv->dmabuf_size[1]; + } +#endif + + outl(inl(devpriv->iobase_a + AMCC_OP_REG_MCSR) & (~EN_A2P_TRANSFERS), + devpriv->iobase_a + AMCC_OP_REG_MCSR); /* stop DMA */ + outl(devpriv->dmabuf_hw[0], devpriv->iobase_a + AMCC_OP_REG_MWAR); + outl(devpriv->dmabuf_use_size[0], devpriv->iobase_a + AMCC_OP_REG_MWTC); + /* 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); */ + + outl(inl(devpriv->iobase_a + + AMCC_OP_REG_MCSR) | RESET_A2P_FLAGS | A2P_HI_PRIORITY | + EN_A2P_TRANSFERS, devpriv->iobase_a + AMCC_OP_REG_MCSR); + 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_docmd_sampl(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + + switch (devpriv->ai_do) { + case 1: + devpriv->AdControlReg |= AdControl_TmrTr; + break; + case 2: + comedi_error(dev, "pci9118_ai_docmd_sampl() mode 2 bug!\n"); + return -EIO; + case 3: + devpriv->AdControlReg |= AdControl_ExtM; + break; + case 4: + comedi_error(dev, "pci9118_ai_docmd_sampl() mode 4 bug!\n"); + return -EIO; + default: + comedi_error(dev, + "pci9118_ai_docmd_sampl() mode number bug!\n"); + return -EIO; + } + + if (devpriv->ai12_startstop) + pci9118_exttrg_add(dev, EXTTRG_AI); + /* activate EXT trigger */ + + if ((devpriv->ai_do == 1) || (devpriv->ai_do == 2)) + devpriv->IntControlReg |= Int_Timer; + + devpriv->AdControlReg |= AdControl_Int; + + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | 0x1f00, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow INT in AMCC */ + + if (!(devpriv->ai12_startstop & (START_AI_EXT | START_AI_INT))) { + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + if (devpriv->ai_do != 3) { + pci9118_start_pacer(dev, devpriv->ai_do); + devpriv->AdControlReg |= AdControl_SoftG; + } + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + } + + return 0; +} + +static int pci9118_ai_docmd_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + Compute_and_setup_dma(dev, s); + + switch (devpriv->ai_do) { + case 1: + devpriv->AdControlReg |= + ((AdControl_TmrTr | AdControl_Dma) & 0xff); + break; + case 2: + devpriv->AdControlReg |= + ((AdControl_TmrTr | AdControl_Dma) & 0xff); + devpriv->AdFunctionReg = + AdFunction_PDTrg | AdFunction_PETrg | AdFunction_BM | + AdFunction_BS; + if (cmd->convert_src == TRIG_NOW && !devpriv->softsshdelay) + devpriv->AdFunctionReg |= AdFunction_BSSH; + outl(devpriv->ai_n_realscanlen, dev->iobase + PCI9118_BURST); + break; + case 3: + devpriv->AdControlReg |= + ((AdControl_ExtM | AdControl_Dma) & 0xff); + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + break; + case 4: + devpriv->AdControlReg |= + ((AdControl_TmrTr | AdControl_Dma) & 0xff); + devpriv->AdFunctionReg = + AdFunction_PDTrg | AdFunction_PETrg | AdFunction_AM; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + outl(0x30, dev->iobase + PCI9118_CNTCTRL); + outl((devpriv->dmabuf_hw[0] >> 1) & 0xff, + dev->iobase + PCI9118_CNT0); + outl((devpriv->dmabuf_hw[0] >> 9) & 0xff, + dev->iobase + PCI9118_CNT0); + devpriv->AdFunctionReg |= AdFunction_Start; + break; + default: + comedi_error(dev, "pci9118_ai_docmd_dma() mode number bug!\n"); + return -EIO; + } + + if (devpriv->ai12_startstop) { + pci9118_exttrg_add(dev, EXTTRG_AI); + /* activate EXT trigger */ + } + + outl(0x02000000 | AINT_WRITE_COMPL, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + + if (!(devpriv->ai12_startstop & (START_AI_EXT | START_AI_INT))) { + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + if (devpriv->ai_do != 3) { + pci9118_start_pacer(dev, devpriv->ai_do); + devpriv->AdControlReg |= AdControl_SoftG; + } + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + } + + return 0; +} + +static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int addchans = 0; + int ret = 0; + + devpriv->ai12_startstop = 0; + devpriv->ai_flags = cmd->flags; + devpriv->ai_add_front = 0; + devpriv->ai_add_back = 0; + devpriv->ai_maskerr = 0x10e; + + /* 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->start_src == TRIG_INT) { + devpriv->ai12_startstop |= START_AI_INT; + s->async->inttrig = pci9118_ai_inttrig; + } + 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 & TRIG_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 & TRIG_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 < this_board->ai_ns_min) + cmd->convert_arg = this_board->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; + if (!setup_channel_list(dev, s, cmd->chanlist_len, + cmd->chanlist, 0, devpriv->ai_add_front, + devpriv->ai_add_back, devpriv->usedma)) + return -EINVAL; + + /* compute timers settings */ + /* + * simplest way, fr=4Mhz/(tim1*tim2), + * channel manipulation without timers effect + */ + if (((cmd->scan_begin_src == TRIG_FOLLOW) || + (cmd->scan_begin_src == TRIG_EXT) || + (cmd->scan_begin_src == TRIG_INT)) && + (cmd->convert_src == TRIG_TIMER)) { + /* both timer is used for one time */ + if (cmd->scan_begin_src == TRIG_EXT) + devpriv->ai_do = 4; + else + devpriv->ai_do = 1; + pci9118_calc_divisors(devpriv->ai_do, dev, s, + &cmd->scan_begin_arg, &cmd->convert_arg, + devpriv->ai_flags, + devpriv->ai_n_realscanlen, + &devpriv->ai_divisor1, + &devpriv->ai_divisor2, + devpriv->ai_add_front); + } + + if ((cmd->scan_begin_src == TRIG_TIMER) && + ((cmd->convert_src == TRIG_TIMER) || + (cmd->convert_src == TRIG_NOW))) { + /* double timed action */ + if (!devpriv->usedma) { + comedi_error(dev, + "cmd->scan_begin_src=TRIG_TIMER works " + "only with bus mastering!"); + return -EIO; + } + + devpriv->ai_do = 2; + pci9118_calc_divisors(devpriv->ai_do, dev, s, + &cmd->scan_begin_arg, &cmd->convert_arg, + devpriv->ai_flags, + devpriv->ai_n_realscanlen, + &devpriv->ai_divisor1, + &devpriv->ai_divisor2, + devpriv->ai_add_front); + } + + if ((cmd->scan_begin_src == TRIG_FOLLOW) + && (cmd->convert_src == TRIG_EXT)) { + devpriv->ai_do = 3; + } + + pci9118_start_pacer(dev, -1); /* stop pacer */ + + devpriv->AdControlReg = 0; /* + * bipolar, S.E., use 8254, stop 8354, + * internal trigger, soft trigger, + * disable DMA + */ + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + /* + * positive triggers, no S&H, no burst, + * burst stop, no post trigger, + * no about trigger, trigger stop + */ + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + udelay(1); + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + inl(dev->iobase + PCI9118_ADSTAT); /* + * flush A/D and INT + * status register + */ + inl(dev->iobase + PCI9118_INTSRC); + + devpriv->ai_act_scan = 0; + devpriv->ai_act_dmapos = 0; + s->async->cur_chan = 0; + + if (devpriv->usedma) + ret = pci9118_ai_docmd_dma(dev, s); + else + ret = pci9118_ai_docmd_sampl(dev, s); + + return ret; +} + +static int pci9118_reset(struct comedi_device *dev) +{ + struct pci9118_private *devpriv = dev->private; + + devpriv->IntControlReg = 0; + devpriv->exttrg_users = 0; + inl(dev->iobase + PCI9118_INTCTRL); + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + /* disable interrupts source */ + outl(0x30, dev->iobase + PCI9118_CNTCTRL); +/* outl(0xb4, dev->iobase + PCI9118_CNTCTRL); */ + pci9118_start_pacer(dev, 0); /* stop 8254 counters */ + devpriv->AdControlReg = 0; + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* + * bipolar, S.E., use 8254, + * stop 8354, internal trigger, + * soft trigger, + * disable INT and DMA + */ + outl(0, dev->iobase + PCI9118_BURST); + outl(1, dev->iobase + PCI9118_SCANMOD); + outl(2, dev->iobase + PCI9118_SCANMOD); /* reset scan queue */ + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + /* + * positive triggers, no S&H, + * no burst, burst stop, + * no post trigger, + * no about trigger, + * trigger stop + */ + + devpriv->ao_data[0] = 2047; + devpriv->ao_data[1] = 2047; + outl(devpriv->ao_data[0], dev->iobase + PCI9118_DA1); + /* reset A/D outs to 0V */ + outl(devpriv->ao_data[1], dev->iobase + PCI9118_DA2); + outl(0, dev->iobase + PCI9118_DO); /* reset digi outs to L */ + udelay(10); + inl(dev->iobase + PCI9118_AD_DATA); + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + outl(0, dev->iobase + PCI9118_INTSRC); /* remove INT requests */ + inl(dev->iobase + PCI9118_ADSTAT); /* flush A/D status register */ + inl(dev->iobase + PCI9118_INTSRC); /* flush INT requests */ + devpriv->AdControlReg = 0; + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* + * bipolar, S.E., use 8254, + * stop 8354, internal trigger, + * soft trigger, + * disable INT and DMA + */ + + devpriv->exttrg_users = 0; + + return 0; +} + +/* + * FIXME - this is pretty ineffective because all the supported board types + * have the same device ID! + */ +static const struct boardtype *pci9118_find_boardinfo(struct pci_dev *pcidev) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(boardtypes); i++) + if (pcidev->device == boardtypes[i].device_id) + return &boardtypes[i]; + return NULL; +} + +static struct pci_dev *pci9118_find_pci(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct boardtype *this_board = comedi_board(dev); + 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 != this_board->device_id) + 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 int pci9118_common_attach(struct comedi_device *dev, int disable_irq, + int master, int ext_mux, int softsshdelay, + int hw_err_mask) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci9118_private *devpriv = dev->private; + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret, pages, i; + u16 u16w; + + dev->board_name = this_board->name; + ret = comedi_pci_enable(dev); + if (ret) + return ret; + if (master) + pci_set_master(pcidev); + + devpriv->iobase_a = pci_resource_start(pcidev, 0); + dev->iobase = pci_resource_start(pcidev, 2); + + pci9118_reset(dev); + + if (master) { /* alloc DMA buffers */ + devpriv->dma_doublebuf = 0; + for (i = 0; i < 2; i++) { + for (pages = 4; pages >= 0; pages--) { + devpriv->dmabuf_virt[i] = + (unsigned short *) + __get_free_pages(GFP_KERNEL, pages); + if (devpriv->dmabuf_virt[i]) + break; + } + if (devpriv->dmabuf_virt[i]) { + devpriv->dmabuf_pages[i] = pages; + devpriv->dmabuf_size[i] = PAGE_SIZE * pages; + devpriv->dmabuf_hw[i] = + virt_to_bus((void *) + devpriv->dmabuf_virt[i]); + } + } + if (!devpriv->dmabuf_virt[0]) { + dev_warn(dev->class_dev, + "Can't allocate DMA buffer, DMA disabled!\n"); + master = 0; + } + if (devpriv->dmabuf_virt[1]) + devpriv->dma_doublebuf = 1; + } + devpriv->master = master; + + 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 = ext_mux; + } 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 */ + + if (!disable_irq && pcidev->irq) { + ret = request_irq(pcidev->irq, pci9118_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]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; + if (devpriv->usemux) + s->n_chan = devpriv->usemux; + else + s->n_chan = this_board->n_aichan; + + s->maxdata = this_board->ai_maxdata; + s->range_table = this_board->rangelist_ai; + s->insn_read = pci9118_insn_read_ai; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = this_board->n_aichanlist; + s->do_cmdtest = pci9118_ai_cmdtest; + s->do_cmd = pci9118_ai_cmd; + s->cancel = pci9118_ai_cancel; + s->munge = pci9118_ai_munge; + } + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->rangelist_ao; + s->insn_write = pci9118_insn_write_ao; + s->insn_read = pci9118_insn_read_ao; + + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 1; + s->len_chanlist = 4; + s->range_table = &range_digital; + s->insn_bits = pci9118_insn_bits_di; + + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 1; + s->len_chanlist = 4; + s->range_table = &range_digital; + s->insn_bits = pci9118_insn_bits_do; + + devpriv->ai_maskharderr = 0x10a; + /* default measure crash condition */ + if (hw_err_mask) /* disable some requested */ + devpriv->ai_maskharderr &= ~hw_err_mask; + + return 0; +} + +static int pci9118_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pci9118_private *devpriv; + struct pci_dev *pcidev; + int ext_mux, disable_irq, master, softsshdelay, hw_err_mask; + + ext_mux = it->options[2]; + master = ((it->options[3] & 1) == 0); + disable_irq = ((it->options[3] & 2) != 0); + softsshdelay = it->options[4]; + hw_err_mask = it->options[5]; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + pcidev = pci9118_find_pci(dev, it); + if (!pcidev) + return -EIO; + comedi_set_hw_dev(dev, &pcidev->dev); + + return pci9118_common_attach(dev, disable_irq, master, ext_mux, + softsshdelay, hw_err_mask); +} + +static int pci9118_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct pci9118_private *devpriv; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + dev->board_ptr = pci9118_find_boardinfo(pcidev); + if (dev->board_ptr == NULL) { + dev_err(dev->class_dev, + "adl_pci9118: cannot determine board type for pci %s\n", + pci_name(pcidev)); + return -EINVAL; + } + /* + * 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); + /* Don't disable irq, use bus master, no external mux, + * no sample-hold delay, no error mask. */ + return pci9118_common_attach(dev, 0, 1, 0, 0, 0); +} + +static void pci9118_detach(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct pci9118_private *devpriv = dev->private; + + if (devpriv) { + if (dev->iobase) + pci9118_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv->dmabuf_virt[0]) + free_pages((unsigned long)devpriv->dmabuf_virt[0], + devpriv->dmabuf_pages[0]); + if (devpriv->dmabuf_virt[1]) + free_pages((unsigned long)devpriv->dmabuf_virt[1], + devpriv->dmabuf_pages[1]); + } + comedi_pci_disable(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(boardtypes), + .board_name = &boardtypes[0].name, + .offset = sizeof(struct boardtype), +}; + +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); +} + +static const struct pci_device_id adl_pci9118_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMCC, 0x80d9) }, + { 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 00000000000..b4ea37704ea --- /dev/null +++ b/drivers/staging/comedi/drivers/adq12b.c @@ -0,0 +1,293 @@ +/* + 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_SIZE 16 + +#define ADQ12B_CTREG 0x00 +#define ADQ12B_STINR 0x00 +#define ADQ12B_OUTBR 0x04 +#define ADQ12B_ADLOW 0x08 +#define ADQ12B_ADHIG 0x09 +#define ADQ12B_CONT0 0x0c +#define ADQ12B_CONT1 0x0d +#define ADQ12B_CONT2 0x0e +#define ADQ12B_COWORD 0x0f + +/* mask of the bit at STINR to check end of conversion */ +#define ADQ12B_EOC 0x20 + +/* 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 { + int unipolar; /* option 2 of comedi_config (1 is iobase) */ + int differential; /* option 3 of comedi_config */ + int last_channel; + int last_range; +}; + +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_EOC) + return 0; + return -EBUSY; +} + +static int adq12b_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + struct adq12b_private *devpriv = dev->private; + int n; + int range, channel; + unsigned char hi, lo, status; + int ret; + + /* change channel and range only if it is different from the previous */ + range = CR_RANGE(insn->chanspec); + channel = CR_CHAN(insn->chanspec); + if (channel != devpriv->last_channel || range != devpriv->last_range) { + outb((range << 4) | channel, dev->iobase + ADQ12B_CTREG); + udelay(50); /* wait for the mux to settle */ + } + + /* trigger conversion */ + status = inb(dev->iobase + ADQ12B_ADLOW); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + + /* wait for end of conversion */ + ret = comedi_timeout(dev, s, insn, adq12b_ai_eoc, 0); + if (ret) + return ret; + + /* read data */ + hi = inb(dev->iobase + ADQ12B_ADHIG); + lo = inb(dev->iobase + ADQ12B_ADLOW); + + data[n] = (hi << 8) | lo; + + } + + /* return the number of samples read/written */ + return 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) & (0x1f)); + + 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], ADQ12B_SIZE); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->unipolar = it->options[1]; + devpriv->differential = it->options[2]; + /* + * initialize channel and range to -1 so we make sure we + * always write at least once to the CTREG in the instruction + */ + devpriv->last_channel = -1; + devpriv->last_range = -1; + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + if (devpriv->differential) { + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 8; + } else { + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 16; + } + + if (devpriv->unipolar) + s->range_table = &range_adq12b_ai_unipolar; + else + s->range_table = &range_adq12b_ai_bipolar; + + s->maxdata = 0xfff; + + s->len_chanlist = 4; /* This is the maximum chanlist length that + the board can handle */ + s->insn_read = adq12b_ai_rinsn; + + s = &dev->subdevices[1]; + /* digital input subdevice */ + 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; + + s = &dev->subdevices[2]; + /* digital output subdevice */ + 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 00000000000..602b7a1e40e --- /dev/null +++ b/drivers/staging/comedi/drivers/adv_pci1710.c @@ -0,0 +1,1338 @@ +/* + * comedi/drivers/adv_pci1710.c + * + * Author: Michal Dobes <dobes@tesnet.cz> + * + * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn> + * for testing and informations. + * + * 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/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "8253.h" +#include "amcc_s5933.h" + +/* hardware types of the cards */ +#define TYPE_PCI171X 0 +#define TYPE_PCI1713 2 +#define TYPE_PCI1720 3 + +#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 + +#define PCI171x_CNT0 24 /* R/W: 8254 counter 0 */ +#define PCI171x_CNT1 26 /* R/W: 8254 counter 1 */ +#define PCI171x_CNT2 28 /* R/W: 8254 counter 2 */ +#define PCI171x_CNTCTRL 30 /* W: 8254 counter control */ + +/* 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 */ +/* bits from counter control register (PCI171x_CNTCTRL) */ +#define Counter_BCD 0x0001 /* 0 = binary counter, 1 = BCD counter */ +#define Counter_M0 0x0002 /* M0-M2 select modes 0-5 */ +#define Counter_M1 0x0004 /* 000 = mode 0, 010 = mode 2 ... */ +#define Counter_M2 0x0008 +#define Counter_RW0 0x0010 /* RW0/RW1 select read/write mode */ +#define Counter_RW1 0x0020 +#define Counter_SC0 0x0040 /* Select Counter. Only 00 or 11 may */ +#define Counter_SC1 0x0080 /* be used, 00 for CNT0, + * 11 for read-back command */ + +#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 range_pci1720 = { + 4, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static const struct comedi_lrange range_pci171x_da = { + 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 */ + char have_irq; /* 1=card support IRQ */ + char cardtype; /* 0=1710& co. 2=1713, ... */ + int n_aichan; /* num of A/D chans */ + int n_aichand; /* num of A/D chans in diff mode */ + int n_aochan; /* num of D/A chans */ + int n_dichan; /* num of DI chans */ + int n_dochan; /* num of DO chans */ + int n_counter; /* num of counters */ + int ai_maxdata; /* resolution of A/D */ + int ao_maxdata; /* resolution of D/A */ + const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */ + const char *rangecode_ai; /* range codes for programming */ + const struct comedi_lrange *rangelist_ao; /* rangelist for D/A */ + unsigned int ai_ns_min; /* max sample speed of card v ns */ + unsigned int fifo_half_size; /* size of FIFO/2 */ +}; + +static const struct boardtype boardtypes[] = { + [BOARD_PCI1710] = { + .name = "pci1710", + .have_irq = 1, + .cardtype = TYPE_PCI171X, + .n_aichan = 16, + .n_aichand = 8, + .n_aochan = 2, + .n_dichan = 16, + .n_dochan = 16, + .n_counter = 1, + .ai_maxdata = 0x0fff, + .ao_maxdata = 0x0fff, + .rangelist_ai = &range_pci1710_3, + .rangecode_ai = range_codes_pci1710_3, + .rangelist_ao = &range_pci171x_da, + .ai_ns_min = 10000, + .fifo_half_size = 2048, + }, + [BOARD_PCI1710HG] = { + .name = "pci1710hg", + .have_irq = 1, + .cardtype = TYPE_PCI171X, + .n_aichan = 16, + .n_aichand = 8, + .n_aochan = 2, + .n_dichan = 16, + .n_dochan = 16, + .n_counter = 1, + .ai_maxdata = 0x0fff, + .ao_maxdata = 0x0fff, + .rangelist_ai = &range_pci1710hg, + .rangecode_ai = range_codes_pci1710hg, + .rangelist_ao = &range_pci171x_da, + .ai_ns_min = 10000, + .fifo_half_size = 2048, + }, + [BOARD_PCI1711] = { + .name = "pci1711", + .have_irq = 1, + .cardtype = TYPE_PCI171X, + .n_aichan = 16, + .n_aochan = 2, + .n_dichan = 16, + .n_dochan = 16, + .n_counter = 1, + .ai_maxdata = 0x0fff, + .ao_maxdata = 0x0fff, + .rangelist_ai = &range_pci17x1, + .rangecode_ai = range_codes_pci17x1, + .rangelist_ao = &range_pci171x_da, + .ai_ns_min = 10000, + .fifo_half_size = 512, + }, + [BOARD_PCI1713] = { + .name = "pci1713", + .have_irq = 1, + .cardtype = TYPE_PCI1713, + .n_aichan = 32, + .n_aichand = 16, + .ai_maxdata = 0x0fff, + .rangelist_ai = &range_pci1710_3, + .rangecode_ai = range_codes_pci1710_3, + .ai_ns_min = 10000, + .fifo_half_size = 2048, + }, + [BOARD_PCI1720] = { + .name = "pci1720", + .cardtype = TYPE_PCI1720, + .n_aochan = 4, + .ao_maxdata = 0x0fff, + .rangelist_ao = &range_pci1720, + }, + [BOARD_PCI1731] = { + .name = "pci1731", + .have_irq = 1, + .cardtype = TYPE_PCI171X, + .n_aichan = 16, + .n_dichan = 16, + .n_dochan = 16, + .ai_maxdata = 0x0fff, + .rangelist_ai = &range_pci17x1, + .rangecode_ai = range_codes_pci17x1, + .ai_ns_min = 10000, + .fifo_half_size = 512, + }, +}; + +struct pci1710_private { + unsigned int CntrlReg; /* Control register */ + unsigned int ai_act_scan; /* how many scans we finished */ + unsigned char ai_et; + unsigned int ai_et_CntrlReg; + unsigned int ai_et_MuxVal; + unsigned int next_divisor1; + unsigned int next_divisor2; + unsigned int divisor1; + unsigned int divisor2; + 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 */ + unsigned short ao_data[4]; /* data output buffer */ + unsigned int cnt0_write_wait; /* after a write, wait for update of the + * internal state */ +}; + +/* used for gain list programming */ +static const unsigned int muxonechan[] = { + 0x0000, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, + 0x0808, 0x0909, 0x0a0a, 0x0b0b, 0x0c0c, 0x0d0d, 0x0e0e, 0x0f0f, + 0x1010, 0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616, 0x1717, + 0x1818, 0x1919, 0x1a1a, 0x1b1b, 0x1c1c, 0x1d1d, 0x1e1e, 0x1f1f +}; + +static int pci171x_ai_dropout(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, + unsigned int val) +{ + const struct boardtype *board = comedi_board(dev); + struct pci1710_private *devpriv = dev->private; + + if (board->cardtype != TYPE_PCI1713) { + if ((val & 0xf000) != devpriv->act_chanlist[chan]) { + dev_err(dev->class_dev, + "A/D data droput: received from channel %d, expected %d\n", + (val >> 12) & 0xf, + (devpriv->act_chanlist[chan] >> 12) & 0xf); + return -ENODATA; + } + } + return 0; +} + +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 setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan, + unsigned int seglen) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci1710_private *devpriv = dev->private; + unsigned int i, range, chanprog; + + for (i = 0; i < seglen; i++) { /* store range list to card */ + chanprog = muxonechan[CR_CHAN(chanlist[i])]; + outw(chanprog, dev->iobase + PCI171x_MUX); /* select channel */ + range = this_board->rangecode_ai[CR_RANGE(chanlist[i])]; + if (CR_AREF(chanlist[i]) == AREF_DIFF) + range |= 0x0020; + outw(range, dev->iobase + PCI171x_RANGE); /* select gain */ + devpriv->act_chanlist[i] = + (CR_CHAN(chanlist[i]) << 12) & 0xf000; + } + for ( ; i < n_chan; i++) { /* store remainder of channel list */ + devpriv->act_chanlist[i] = + (CR_CHAN(chanlist[i]) << 12) & 0xf000; + } + + devpriv->ai_et_MuxVal = + CR_CHAN(chanlist[0]) | (CR_CHAN(chanlist[seglen - 1]) << 8); + /* select channel interval to scan */ + 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_insn_read_ai(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); + 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); + + setup_channel_list(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; + + val = inw(dev->iobase + PCI171x_AD_DATA); + ret = pci171x_ai_dropout(dev, s, chan, val); + if (ret) + break; + + data[i] = val & s->maxdata; + } + + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + return ret ? ret : insn->n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + unsigned int val; + int n, chan, range, ofs; + + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + if (chan) { + devpriv->da_ranges &= 0xfb; + devpriv->da_ranges |= (range << 2); + outw(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); + ofs = PCI171x_DA2; + } else { + devpriv->da_ranges &= 0xfe; + devpriv->da_ranges |= range; + outw(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); + ofs = PCI171x_DA1; + } + val = devpriv->ao_data[chan]; + + for (n = 0; n < insn->n; n++) { + val = data[n]; + outw(val, dev->iobase + ofs); + } + + devpriv->ao_data[chan] = val; + + return n; + +} + +/* +============================================================================== +*/ +static int pci171x_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + int n, chan; + + chan = CR_CHAN(insn->chanspec); + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_bits_di(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_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)) + outw(s->state, dev->iobase + PCI171x_DO); + + data[1] = s->state; + + return insn->n; +} + +static void pci171x_start_pacer(struct comedi_device *dev, + bool load_counters) +{ + struct pci1710_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + PCI171X_TIMER_BASE; + + i8254_set_mode(timer_base, 1, 2, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 1, 1, I8254_MODE2 | I8254_BINARY); + + if (load_counters) { + i8254_write(timer_base, 1, 2, devpriv->divisor2); + i8254_write(timer_base, 1, 1, devpriv->divisor1); + } +} + +/* +============================================================================== +*/ +static int pci171x_insn_counter_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int msb, lsb, ccntrl; + int i; + + ccntrl = 0xD2; /* count only */ + for (i = 0; i < insn->n; i++) { + outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); + + lsb = inw(dev->iobase + PCI171x_CNT0) & 0xFF; + msb = inw(dev->iobase + PCI171x_CNT0) & 0xFF; + + data[0] = lsb | (msb << 8); + } + + return insn->n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_counter_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + uint msb, lsb, ccntrl, status; + + lsb = data[0] & 0x00FF; + msb = (data[0] & 0xFF00) >> 8; + + /* write lsb, then msb */ + outw(lsb, dev->iobase + PCI171x_CNT0); + outw(msb, dev->iobase + PCI171x_CNT0); + + if (devpriv->cnt0_write_wait) { + /* wait for the new count to be loaded */ + ccntrl = 0xE2; + do { + outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); + status = inw(dev->iobase + PCI171x_CNT0) & 0xFF; + } while (status & 0x40); + } + + return insn->n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_counter_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ +#ifdef unused + /* This doesn't work like a normal Comedi counter config */ + struct pci1710_private *devpriv = dev->private; + uint ccntrl = 0; + + devpriv->cnt0_write_wait = data[0] & 0x20; + + /* internal or external clock? */ + if (!(data[0] & 0x10)) { /* internal */ + devpriv->CntrlReg &= ~Control_CNT0; + } else { + devpriv->CntrlReg |= Control_CNT0; + } + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + + if (data[0] & 0x01) + ccntrl |= Counter_M0; + if (data[0] & 0x02) + ccntrl |= Counter_M1; + if (data[0] & 0x04) + ccntrl |= Counter_M2; + if (data[0] & 0x08) + ccntrl |= Counter_BCD; + ccntrl |= Counter_RW0; /* set read/write mode */ + ccntrl |= Counter_RW1; + outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); +#endif + + return 1; +} + +/* +============================================================================== +*/ +static int pci1720_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + unsigned int val; + int n, rangereg, chan; + + chan = CR_CHAN(insn->chanspec); + rangereg = devpriv->da_ranges & (~(0x03 << (chan << 1))); + rangereg |= (CR_RANGE(insn->chanspec) << (chan << 1)); + if (rangereg != devpriv->da_ranges) { + outb(rangereg, dev->iobase + PCI1720_RANGE); + devpriv->da_ranges = rangereg; + } + val = devpriv->ao_data[chan]; + + for (n = 0; n < insn->n; n++) { + val = data[n]; + outw(val, dev->iobase + PCI1720_DA0 + (chan << 1)); + outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */ + } + + devpriv->ao_data[chan] = val; + + return n; +} + +/* +============================================================================== +*/ +static int pci171x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci1710_private *devpriv = dev->private; + + switch (this_board->cardtype) { + default: + devpriv->CntrlReg &= Control_CNT0; + devpriv->CntrlReg |= Control_SW; + + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); /* reset any operations */ + pci171x_start_pacer(dev, false); + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + break; + } + + devpriv->ai_act_scan = 0; + s->async->cur_chan = 0; + + return 0; +} + +static void pci1710_handle_every_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci1710_private *devpriv = dev->private; + 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_EOA | COMEDI_CB_ERROR; + cfc_handle_events(dev, s); + 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_EOA | COMEDI_CB_ERROR; + cfc_handle_events(dev, s); + return; + } + + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ + + for (; !(inw(dev->iobase + PCI171x_STATUS) & Status_FE);) { + val = inw(dev->iobase + PCI171x_AD_DATA); + ret = pci171x_ai_dropout(dev, s, s->async->cur_chan, val); + if (ret) { + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + break; + } + + comedi_buf_put(s, val & s->maxdata); + + s->async->cur_chan++; + if (s->async->cur_chan >= cmd->chanlist_len) + s->async->cur_chan = 0; + + + if (s->async->cur_chan == 0) { /* one scan done */ + devpriv->ai_act_scan++; + if (cmd->stop_src == TRIG_COUNT && + devpriv->ai_act_scan >= cmd->stop_arg) { + /* all data sampled */ + s->async->events |= COMEDI_CB_EOA; + break; + } + } + } + + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ + + cfc_handle_events(dev, s); +} + +/* +============================================================================== +*/ +static int move_block_from_fifo(struct comedi_device *dev, + struct comedi_subdevice *s, int n, int turn) +{ + struct pci1710_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int val; + int ret; + int i; + + for (i = 0; i < n; i++) { + val = inw(dev->iobase + PCI171x_AD_DATA); + + ret = pci171x_ai_dropout(dev, s, s->async->cur_chan, val); + if (ret) { + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + return ret; + } + + comedi_buf_put(s, val & s->maxdata); + + s->async->cur_chan++; + if (s->async->cur_chan >= cmd->chanlist_len) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + } + } + return 0; +} + +static void pci1710_handle_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci1710_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int m, samplesinbuf; + + m = inw(dev->iobase + PCI171x_STATUS); + if (!(m & Status_FH)) { + dev_dbg(dev->class_dev, "A/D FIFO not half full! (%4x)\n", m); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + cfc_handle_events(dev, s); + return; + } + if (m & Status_FF) { + dev_dbg(dev->class_dev, + "A/D FIFO Full status (Fatal Error!) (%4x)\n", m); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + cfc_handle_events(dev, s); + return; + } + + samplesinbuf = this_board->fifo_half_size; + if (samplesinbuf * sizeof(short) >= s->async->prealloc_bufsz) { + m = s->async->prealloc_bufsz / sizeof(short); + if (move_block_from_fifo(dev, s, m, 0)) + return; + samplesinbuf -= m; + } + + if (samplesinbuf) { + if (move_block_from_fifo(dev, s, samplesinbuf, 1)) + return; + } + + if (cmd->stop_src == TRIG_COUNT && + devpriv->ai_act_scan >= cmd->stop_arg) { + /* all data sampled */ + s->async->events |= COMEDI_CB_EOA; + cfc_handle_events(dev, s); + return; + } + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ + + cfc_handle_events(dev, s); +} + +/* +============================================================================== +*/ +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); + pci171x_start_pacer(dev, true); + return IRQ_HANDLED; + } + + if (cmd->flags & TRIG_WAKE_EOS) + pci1710_handle_every_sample(dev, s); + else + pci1710_handle_fifo(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_start_pacer(dev, false); + + setup_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len, + devpriv->saved_seglen); + + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + devpriv->ai_act_scan = 0; + s->async->cur_chan = 0; + + devpriv->CntrlReg &= Control_CNT0; + if ((cmd->flags & TRIG_WAKE_EOS) == 0) + devpriv->CntrlReg |= Control_ONEFH; + + devpriv->divisor1 = devpriv->next_divisor1; + devpriv->divisor2 = devpriv->next_divisor2; + + if (cmd->convert_src == TRIG_TIMER) { + 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) + pci171x_start_pacer(dev, 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) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci1710_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* step 2a: make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + this_board->ai_ns_min); + else /* TRIG_FOLLOW */ + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, + &devpriv->next_divisor1, + &devpriv->next_divisor2, + &arg, cmd->flags); + err |= cfc_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_reset(struct comedi_device *dev) +{ + const struct boardtype *this_board = comedi_board(dev); + struct pci1710_private *devpriv = dev->private; + + outw(0x30, dev->iobase + PCI171x_CNTCTRL); + devpriv->CntrlReg = Control_SW | Control_CNT0; /* Software trigger, CNT0=external */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); /* reset any operations */ + outb(0, dev->iobase + PCI171x_CLRFIFO); /* clear FIFO */ + outb(0, dev->iobase + PCI171x_CLRINT); /* clear INT request */ + pci171x_start_pacer(dev, false); + devpriv->da_ranges = 0; + if (this_board->n_aochan) { + outb(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); /* set DACs to 0..5V */ + outw(0, dev->iobase + PCI171x_DA1); /* set DA outputs to 0V */ + devpriv->ao_data[0] = 0x0000; + if (this_board->n_aochan > 1) { + outw(0, dev->iobase + PCI171x_DA2); + devpriv->ao_data[1] = 0x0000; + } + } + 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; + + outb(Syncont_SC0, dev->iobase + PCI1720_SYNCONT); /* set synchronous output mode */ + devpriv->da_ranges = 0xAA; + outb(devpriv->da_ranges, dev->iobase + PCI1720_RANGE); /* set all ranges to +/-5V */ + 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 */ + devpriv->ao_data[0] = 0x0800; + devpriv->ao_data[1] = 0x0800; + devpriv->ao_data[2] = 0x0800; + devpriv->ao_data[3] = 0x0800; + return 0; +} + +/* +============================================================================== +*/ +static int pci1710_reset(struct comedi_device *dev) +{ + const struct boardtype *this_board = comedi_board(dev); + + switch (this_board->cardtype) { + case TYPE_PCI1720: + return pci1720_reset(dev); + default: + 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 *this_board = NULL; + struct pci1710_private *devpriv; + struct comedi_subdevice *s; + int ret, subdev, n_subdevices; + + 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, 2); + + n_subdevices = 0; + if (this_board->n_aichan) + n_subdevices++; + if (this_board->n_aochan) + n_subdevices++; + if (this_board->n_dichan) + n_subdevices++; + if (this_board->n_dochan) + n_subdevices++; + if (this_board->n_counter) + n_subdevices++; + + ret = comedi_alloc_subdevices(dev, n_subdevices); + if (ret) + return ret; + + pci1710_reset(dev); + + if (this_board->have_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 (this_board->n_aichan) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND; + if (this_board->n_aichand) + s->subdev_flags |= SDF_DIFF; + s->n_chan = this_board->n_aichan; + s->maxdata = this_board->ai_maxdata; + s->range_table = this_board->rangelist_ai; + s->insn_read = pci171x_insn_read_ai; + 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 (this_board->n_aochan) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->rangelist_ao; + switch (this_board->cardtype) { + case TYPE_PCI1720: + s->insn_write = pci1720_insn_write_ao; + break; + default: + s->insn_write = pci171x_insn_write_ao; + break; + } + s->insn_read = pci171x_insn_read_ao; + subdev++; + } + + if (this_board->n_dichan) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + s->insn_bits = pci171x_insn_bits_di; + subdev++; + } + + if (this_board->n_dochan) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + s->insn_bits = pci171x_insn_bits_do; + subdev++; + } + + if (this_board->n_counter) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = this_board->n_counter; + s->len_chanlist = this_board->n_counter; + s->maxdata = 0xffff; + s->range_table = &range_unknown; + s->insn_read = pci171x_insn_counter_read; + s->insn_write = pci171x_insn_counter_write; + s->insn_config = pci171x_insn_counter_config; + subdev++; + } + + return 0; +} + +static void pci1710_detach(struct comedi_device *dev) +{ + if (dev->iobase) + pci1710_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(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 low-level driver"); +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 00000000000..07b107d1ab3 --- /dev/null +++ b/drivers/staging/comedi/drivers/adv_pci1723.c @@ -0,0 +1,323 @@ +/* + comedi/drivers/pci1723.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: 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: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + + If bus/slot is not specified, the first supported + PCI device found will be used. + +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 <linux/pci.h> + +#include "../comedidev.h" + +/* all the registers for the pci1723 board */ +#define PCI1723_DA(N) ((N)<<1) /* W: D/A register N (0 to 7) */ + +#define PCI1723_SYN_SET 0x12 /* synchronized set register */ +#define PCI1723_ALL_CHNNELE_SYN_STROBE 0x12 + /* synchronized status register */ + +#define PCI1723_RANGE_CALIBRATION_MODE 0x14 + /* range and calibration mode */ +#define PCI1723_RANGE_CALIBRATION_STATUS 0x14 + /* range and calibration status */ + +#define PCI1723_CONTROL_CMD_CALIBRATION_FUN 0x16 + /* + * SADC control command for + * calibration function + */ +#define PCI1723_STATUS_CMD_CALIBRATION_FUN 0x16 + /* + * SADC control status for + * calibration function + */ + +#define PCI1723_CALIBRATION_PARA_STROBE 0x18 + /* Calibration parameter strobe */ + +#define PCI1723_DIGITAL_IO_PORT_SET 0x1A /* Digital I/O port setting */ +#define PCI1723_DIGITAL_IO_PORT_MODE 0x1A /* Digital I/O port mode */ + +#define PCI1723_WRITE_DIGITAL_OUTPUT_CMD 0x1C + /* Write digital output command */ +#define PCI1723_READ_DIGITAL_INPUT_DATA 0x1C /* Read digital input data */ + +#define PCI1723_WRITE_CAL_CMD 0x1E /* Write calibration command */ +#define PCI1723_READ_CAL_STATUS 0x1E /* Read calibration status */ + +#define PCI1723_SYN_STROBE 0x20 /* Synchronized strobe */ + +#define PCI1723_RESET_ALL_CHN_STROBE 0x22 + /* Reset all D/A channels strobe */ + +#define PCI1723_RESET_CAL_CONTROL_STROBE 0x24 + /* + * Reset the calibration + * controller strobe + */ + +#define PCI1723_CHANGE_CHA_OUTPUT_TYPE_STROBE 0x26 + /* + * Change D/A channels output + * type strobe + */ + +#define PCI1723_SELECT_CALIBRATION 0x28 /* Select the calibration Ref_V */ + +struct pci1723_private { + unsigned char da_range[8]; /* D/A output range for each channel */ + unsigned short ao_data[8]; /* data output buffer */ +}; + +/* + * The pci1723 card reset; + */ +static int pci1723_reset(struct comedi_device *dev) +{ + struct pci1723_private *devpriv = dev->private; + int i; + + outw(0x01, dev->iobase + PCI1723_SYN_SET); + /* set synchronous output mode */ + + for (i = 0; i < 8; i++) { + /* set all outputs to 0V */ + devpriv->ao_data[i] = 0x8000; + outw(devpriv->ao_data[i], dev->iobase + PCI1723_DA(i)); + /* set all ranges to +/- 10V */ + devpriv->da_range[i] = 0; + outw(((devpriv->da_range[i] << 4) | i), + PCI1723_RANGE_CALIBRATION_MODE); + } + + outw(0, dev->iobase + PCI1723_CHANGE_CHA_OUTPUT_TYPE_STROBE); + /* update ranges */ + outw(0, dev->iobase + PCI1723_SYN_STROBE); /* update outputs */ + + /* set asynchronous output mode */ + outw(0, dev->iobase + PCI1723_SYN_SET); + + return 0; +} + +static int pci1723_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci1723_private *devpriv = dev->private; + int n, chan; + + chan = CR_CHAN(insn->chanspec); + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +/* + analog data output; +*/ +static int pci1723_ao_write_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci1723_private *devpriv = dev->private; + int n, chan; + chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { + + devpriv->ao_data[chan] = data[n]; + outw(data[n], dev->iobase + PCI1723_DA(chan)); + } + + return n; +} + +/* + digital i/o config/query +*/ +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; + unsigned short mode; + int ret; + + if (chan < 8) + mask = 0x00ff; + else + mask = 0xff00; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + /* update hardware DIO mode */ + mode = 0x0000; /* assume output */ + if (!(s->io_bits & 0x00ff)) + mode |= 0x0001; /* low byte input */ + if (!(s->io_bits & 0xff00)) + mode |= 0x0002; /* high byte input */ + outw(mode, dev->iobase + PCI1723_DIGITAL_IO_PORT_SET); + + 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_WRITE_DIGITAL_OUTPUT_CMD); + + data[1] = inw(dev->iobase + PCI1723_READ_DIGITAL_INPUT_DATA); + + 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 pci1723_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); + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + dev->write_subdev = s; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 8; + s->maxdata = 0xffff; + s->len_chanlist = 8; + s->range_table = &range_bipolar10; + s->insn_write = pci1723_ao_write_winsn; + s->insn_read = pci1723_insn_read_ao; + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->len_chanlist = 16; + s->range_table = &range_digital; + s->insn_config = pci1723_dio_insn_config; + s->insn_bits = pci1723_dio_insn_bits; + + /* read DIO config */ + switch (inw(dev->iobase + PCI1723_DIGITAL_IO_PORT_MODE) & 0x03) { + case 0x00: /* low byte output, high byte output */ + s->io_bits = 0xFFFF; + break; + case 0x01: /* low byte input, high byte output */ + s->io_bits = 0xFF00; + break; + case 0x02: /* low byte output, high byte input */ + s->io_bits = 0x00FF; + break; + case 0x03: /* low byte input, high byte input */ + s->io_bits = 0x0000; + break; + } + /* read DIO port state */ + s->state = inw(dev->iobase + PCI1723_READ_DIGITAL_INPUT_DATA); + + pci1723_reset(dev); + + return 0; +} + +static void pci1723_detach(struct comedi_device *dev) +{ + if (dev->iobase) + pci1723_reset(dev); + comedi_pci_disable(dev); +} + +static struct comedi_driver adv_pci1723_driver = { + .driver_name = "adv_pci1723", + .module = THIS_MODULE, + .auto_attach = pci1723_auto_attach, + .detach = pci1723_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("Comedi low-level 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 00000000000..af670acb03d --- /dev/null +++ b/drivers/staging/comedi/drivers/adv_pci1724.c @@ -0,0 +1,401 @@ +/* + comedi/drivers/adv_pci1724.c + This is a 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_1724 +Description: Advantech PCI-1724U +Author: Frank Mori Hess <fmh6jj@gmail.com> +Status: works +Updated: 2013-02-09 +Devices: [Advantech] PCI-1724U (adv_pci1724) + +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. + +Configuration options: + None + +Manual configuration of comedi devices is not supported by this driver; +supported PCI devices are configured as comedi devices automatically. + +*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pci.h> + +#include "../comedidev.h" + +#define PCI_VENDOR_ID_ADVANTECH 0x13fe + +#define NUM_AO_CHANNELS 32 + +/* register offsets */ +enum board_registers { + DAC_CONTROL_REG = 0x0, + SYNC_OUTPUT_REG = 0x4, + EEPROM_CONTROL_REG = 0x8, + SYNC_OUTPUT_TRIGGER_REG = 0xc, + BOARD_ID_REG = 0x10 +}; + +/* bit definitions for registers */ +enum dac_control_contents { + DAC_DATA_MASK = 0x3fff, + DAC_DESTINATION_MASK = 0xc000, + DAC_NORMAL_MODE = 0xc000, + DAC_OFFSET_MODE = 0x8000, + DAC_GAIN_MODE = 0x4000, + DAC_CHANNEL_SELECT_MASK = 0xf0000, + DAC_GROUP_SELECT_MASK = 0xf00000 +}; + +static uint32_t dac_data_bits(uint16_t dac_data) +{ + return dac_data & DAC_DATA_MASK; +} + +static uint32_t dac_channel_select_bits(unsigned channel) +{ + return (channel << 16) & DAC_CHANNEL_SELECT_MASK; +} + +static uint32_t dac_group_select_bits(unsigned group) +{ + return (1 << (20 + group)) & DAC_GROUP_SELECT_MASK; +} + +static uint32_t dac_channel_and_group_select_bits(unsigned comedi_channel) +{ + return dac_channel_select_bits(comedi_channel % 8) | + dac_group_select_bits(comedi_channel / 8); +} + +enum sync_output_contents { + SYNC_MODE = 0x1, + DAC_BUSY = 0x2, /* dac state machine is not ready */ +}; + +enum sync_output_trigger_contents { + SYNC_TRIGGER_BITS = 0x0 /* any value works */ +}; + +enum board_id_contents { + BOARD_ID_MASK = 0xf +}; + +static const struct comedi_lrange ao_ranges_1724 = { + 4, { + BIP_RANGE(10), + RANGE_mA(0, 20), + RANGE_mA(4, 20), + RANGE_unitless(0, 1) + } +}; + +/* this structure is for data unique to this hardware driver. */ +struct adv_pci1724_private { + int ao_value[NUM_AO_CHANNELS]; + int offset_value[NUM_AO_CHANNELS]; + int gain_value[NUM_AO_CHANNELS]; +}; + +static int wait_for_dac_idle(struct comedi_device *dev) +{ + static const int timeout = 10000; + int i; + + for (i = 0; i < timeout; ++i) { + if ((inl(dev->iobase + SYNC_OUTPUT_REG) & DAC_BUSY) == 0) + break; + udelay(1); + } + if (i == timeout) { + comedi_error(dev, "Timed out waiting for dac to become idle."); + return -EIO; + } + return 0; +} + +static int set_dac(struct comedi_device *dev, unsigned mode, unsigned channel, + unsigned data) +{ + int retval; + unsigned control_bits; + + retval = wait_for_dac_idle(dev); + if (retval < 0) + return retval; + + control_bits = mode; + control_bits |= dac_channel_and_group_select_bits(channel); + control_bits |= dac_data_bits(data); + outl(control_bits, dev->iobase + DAC_CONTROL_REG); + return 0; +} + +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct adv_pci1724_private *devpriv = dev->private; + int channel = CR_CHAN(insn->chanspec); + int retval; + int i; + + /* turn off synchronous mode */ + outl(0, dev->iobase + SYNC_OUTPUT_REG); + + for (i = 0; i < insn->n; ++i) { + retval = set_dac(dev, DAC_NORMAL_MODE, channel, data[i]); + if (retval < 0) + return retval; + devpriv->ao_value[channel] = data[i]; + } + return insn->n; +} + +static int ao_readback_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct adv_pci1724_private *devpriv = dev->private; + int channel = CR_CHAN(insn->chanspec); + int i; + + if (devpriv->ao_value[channel] < 0) { + comedi_error(dev, + "Cannot read back channels which have not yet been written to."); + return -EIO; + } + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_value[channel]; + + return insn->n; +} + +static int offset_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct adv_pci1724_private *devpriv = dev->private; + int channel = CR_CHAN(insn->chanspec); + int retval; + int i; + + /* turn off synchronous mode */ + outl(0, dev->iobase + SYNC_OUTPUT_REG); + + for (i = 0; i < insn->n; ++i) { + retval = set_dac(dev, DAC_OFFSET_MODE, channel, data[i]); + if (retval < 0) + return retval; + devpriv->offset_value[channel] = data[i]; + } + + return insn->n; +} + +static int offset_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct adv_pci1724_private *devpriv = dev->private; + unsigned int channel = CR_CHAN(insn->chanspec); + int i; + + if (devpriv->offset_value[channel] < 0) { + comedi_error(dev, + "Cannot read back channels which have not yet been written to."); + return -EIO; + } + for (i = 0; i < insn->n; i++) + data[i] = devpriv->offset_value[channel]; + + return insn->n; +} + +static int gain_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct adv_pci1724_private *devpriv = dev->private; + int channel = CR_CHAN(insn->chanspec); + int retval; + int i; + + /* turn off synchronous mode */ + outl(0, dev->iobase + SYNC_OUTPUT_REG); + + for (i = 0; i < insn->n; ++i) { + retval = set_dac(dev, DAC_GAIN_MODE, channel, data[i]); + if (retval < 0) + return retval; + devpriv->gain_value[channel] = data[i]; + } + + return insn->n; +} + +static int gain_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + struct adv_pci1724_private *devpriv = dev->private; + unsigned int channel = CR_CHAN(insn->chanspec); + int i; + + if (devpriv->gain_value[channel] < 0) { + comedi_error(dev, + "Cannot read back channels which have not yet been written to."); + return -EIO; + } + for (i = 0; i < insn->n; i++) + data[i] = devpriv->gain_value[channel]; + + return insn->n; +} + +/* Allocate and initialize the subdevice structures. + */ +static int setup_subdevices(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + int ret; + + 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 = NUM_AO_CHANNELS; + s->maxdata = 0x3fff; + s->range_table = &ao_ranges_1724; + s->insn_read = ao_readback_insn; + s->insn_write = ao_winsn; + + /* offset calibration */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = NUM_AO_CHANNELS; + s->insn_read = offset_read_insn; + s->insn_write = offset_write_insn; + s->maxdata = 0x3fff; + + /* gain calibration */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = NUM_AO_CHANNELS; + s->insn_read = gain_read_insn; + s->insn_write = gain_write_insn; + s->maxdata = 0x3fff; + + return 0; +} + +static int adv_pci1724_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct adv_pci1724_private *devpriv; + int i; + int retval; + unsigned int board_id; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* init software copies of output values to indicate we don't know + * what the output value is since it has never been written. */ + for (i = 0; i < NUM_AO_CHANNELS; ++i) { + devpriv->ao_value[i] = -1; + devpriv->offset_value[i] = -1; + devpriv->gain_value[i] = -1; + } + + retval = comedi_pci_enable(dev); + if (retval) + return retval; + + dev->iobase = pci_resource_start(pcidev, 2); + board_id = inl(dev->iobase + BOARD_ID_REG) & BOARD_ID_MASK; + dev_info(dev->class_dev, "board id: %d\n", board_id); + + retval = setup_subdevices(dev); + if (retval < 0) + return retval; + + dev_info(dev->class_dev, "%s (pci %s) attached, board id: %u\n", + dev->board_name, pci_name(pcidev), board_id); + 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_disable, +}; + +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 00000000000..2d966a87f2e --- /dev/null +++ b/drivers/staging/comedi/drivers/adv_pci_dio.c @@ -0,0 +1,1222 @@ +/* + * 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/pci.h> +#include <linux/delay.h> + +#include "../comedidev.h" + +#include "8255.h" +#include "8253.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 MAX_8254_SUBDEVS 1 /* max number of 8254 counter subdevs per + * card */ + /* (could be more than one 8254 per + * subdevice) */ + +#define SIZE_8254 4 /* 8254 IO space length */ +#define SIZE_8255 4 /* 8255 IO space length */ + +#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 or 8254 chips */ + 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 */ + struct diosubd_data s8254[MAX_8254_SUBDEVS]; /* 8254 subdevices */ + 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, }, + .s8254[0] = { 3, PCI1735_C8254, 1, 0, }, + .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, }, + .s8254[0] = { 3, PCI1751_CNT, 1, 0, }, + .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 valid; /* card is usable */ + 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 pci_8254_insn_read(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; + unsigned int chan, chip, chipchan; + unsigned long flags; + + chan = CR_CHAN(insn->chanspec); /* channel on subdevice */ + chip = chan / 3; /* chip on subdevice */ + chipchan = chan - (3 * chip); /* channel on chip on subdevice */ + spin_lock_irqsave(&s->spin_lock, flags); + data[0] = i8254_read(dev->iobase + d->addr + (SIZE_8254 * chip), + 0, chipchan); + spin_unlock_irqrestore(&s->spin_lock, flags); + return 1; +} + +/* +============================================================================== +*/ +static int pci_8254_insn_write(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; + unsigned int chan, chip, chipchan; + unsigned long flags; + + chan = CR_CHAN(insn->chanspec); /* channel on subdevice */ + chip = chan / 3; /* chip on subdevice */ + chipchan = chan - (3 * chip); /* channel on chip on subdevice */ + spin_lock_irqsave(&s->spin_lock, flags); + i8254_write(dev->iobase + d->addr + (SIZE_8254 * chip), + 0, chipchan, data[0]); + spin_unlock_irqrestore(&s->spin_lock, flags); + return 1; +} + +/* +============================================================================== +*/ +static int pci_8254_insn_config(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; + unsigned int chan, chip, chipchan; + unsigned long iobase; + int ret = 0; + unsigned long flags; + + chan = CR_CHAN(insn->chanspec); /* channel on subdevice */ + chip = chan / 3; /* chip on subdevice */ + chipchan = chan - (3 * chip); /* channel on chip on subdevice */ + iobase = dev->iobase + d->addr + (SIZE_8254 * chip); + spin_lock_irqsave(&s->spin_lock, flags); + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + ret = i8254_set_mode(iobase, 0, chipchan, data[1]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_8254_READ_STATUS: + data[1] = i8254_status(iobase, 0, chipchan); + break; + default: + ret = -EINVAL; + break; + } + spin_unlock_irqrestore(&s->spin_lock, flags); + return ret < 0 ? ret : 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; + } + + comedi_error(dev, "PCI-1760 mailbox request timeout!"); + 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) { + comedi_error(dev, + "bug! this function should not be used for CMD_ClearIMB2 command"); + 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 = comedi_board(dev); + + 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); + /* NO break there! */ + 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); + i8254_set_mode(dev->iobase + PCI1735_C8254, 0, 0, I8254_MODE0); + i8254_set_mode(dev->iobase + PCI1735_C8254, 0, 1, I8254_MODE0); + i8254_set_mode(dev->iobase + PCI1735_C8254, 0, 2, I8254_MODE0); + 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); + /* NO break there! */ + 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 | SDF_GROUND | SDF_COMMON; + 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 | SDF_GROUND | SDF_COMMON; + 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 = comedi_board(dev); + + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | 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 = comedi_board(dev); + + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + 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 int pci_dio_add_8254(struct comedi_device *dev, + struct comedi_subdevice *s, + const struct diosubd_data *d) +{ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = d->chans; + s->maxdata = 65535; + s->len_chanlist = d->chans; + s->insn_read = pci_8254_insn_read; + s->insn_write = pci_8254_insn_write; + s->insn_config = pci_8254_insn_config; + 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, + dev->iobase + + this_board->sdio[i].addr + + SIZE_8255 * j); + 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++; + } + + for (i = 0; i < MAX_8254_SUBDEVS; i++) + if (this_board->s8254[i].chans) { + s = &dev->subdevices[subdev]; + pci_dio_add_8254(dev, s, &this_board->s8254[i]); + subdev++; + } + + if (this_board->cardtype == TYPE_PCI1760) + pci1760_attach(dev); + + devpriv->valid = 1; + + pci_dio_reset(dev); + + return 0; +} + +static void pci_dio_detach(struct comedi_device *dev) +{ + struct pci_dio_private *devpriv = dev->private; + + if (devpriv) { + if (devpriv->valid) + pci_dio_reset(dev); + } + comedi_pci_disable(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 00000000000..324746b1493 --- /dev/null +++ b/drivers/staging/comedi/drivers/aio_aio12_8.c @@ -0,0 +1,272 @@ +/* + + 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, + }, +}; + +struct aio12_8_private { + unsigned int ao_readback[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_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct aio12_8_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int val = devpriv->ao_readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) + data[i] = val; + return insn->n; +} + +static int aio_aio12_8_ao_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct aio12_8_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned long port = dev->iobase + AIO12_8_DAC_REG(chan); + unsigned int val = 0; + 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, port); + } + + devpriv->ao_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 = comedi_board(dev); + struct aio12_8_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; + + 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_read = aio_aio12_8_ao_read; + s->insn_write = aio_aio12_8_ao_write; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + /* 8255 Digital i/o subdevice */ + ret = subdev_8255_init(dev, s, NULL, + dev->iobase + 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 00000000000..781104aa533 --- /dev/null +++ b/drivers/staging/comedi/drivers/aio_iiro_16.c @@ -0,0 +1,114 @@ +/* + + comedi/drivers/aio_iiro_16.c + + Driver for Access I/O Products PC-104 AIO-IIRO-16 Digital 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_iiro_16 +Description: Access I/O Products PC-104 IIRO16 Relay And Isolated Input Board +Author: Zachary Ware <zach.ware@cctechnol.com> +Devices: + [Access I/O] PC-104 AIO12-8 +Status: experimental + +Configuration Options: + [0] - I/O port base address + +*/ + +#include <linux/module.h> +#include "../comedidev.h" + +#define AIO_IIRO_16_SIZE 0x08 +#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 + +static int aio_iiro_16_dio_insn_bits_write(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_dio_insn_bits_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = 0; + data[1] |= inb(dev->iobase + AIO_IIRO_16_INPUT_0_7); + data[1] |= inb(dev->iobase + AIO_IIRO_16_INPUT_8_15) << 8; + + 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], AIO_IIRO_16_SIZE); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = aio_iiro_16_dio_insn_bits_write; + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = aio_iiro_16_dio_insn_bits_read; + + 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 low-level driver"); +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 00000000000..2ba73644461 --- /dev/null +++ b/drivers/staging/comedi/drivers/amcc_s5933.h @@ -0,0 +1,172 @@ +/* + 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 */ +#define INTCSR_INBOX_INTR_STATUS 0x20000 /* read, or write clear inbox full interrupt */ +#define INTCSR_INTR_ASSERTED 0x800000 /* read only, interrupt asserted */ + +/****************************************************************************/ +/* 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_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 00000000000..dc1dee79fc1 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200.c @@ -0,0 +1,305 @@ +/* + 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", + .bustype = isa_bustype, + .mainsize = DIO200_IO_SIZE, + .layout = { + .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", + .bustype = isa_bustype, + .mainsize = DIO200_IO_SIZE, + .layout = { + .n_subdevs = 4, + .sdtype = {sd_8255, sd_8255, sd_8254, sd_intr}, + .sdinfo = {0x00, 0x08, 0x10, 0x01}, + }, + }, + { + .name = "pc215e", + .bustype = isa_bustype, + .mainsize = DIO200_IO_SIZE, + .layout = { + .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", + .bustype = isa_bustype, + .mainsize = DIO200_IO_SIZE, + .layout = { + .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", + .bustype = isa_bustype, + .mainsize = DIO200_IO_SIZE, + .layout = { + .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) +{ + const struct dio200_board *thisboard = comedi_board(dev); + struct dio200_private *devpriv; + unsigned int irq; + int ret; + + irq = it->options[1]; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], thisboard->mainsize); + if (ret) + return ret; + devpriv->io.u.iobase = dev->iobase; + devpriv->io.regtype = io_regtype; + return amplc_dio200_common_attach(dev, irq, 0); +} + +static void dio200_detach(struct comedi_device *dev) +{ + amplc_dio200_common_detach(dev); + comedi_legacy_detach(dev); +} + +static struct comedi_driver amplc_dio200_driver = { + .driver_name = "amplc_dio200", + .module = THIS_MODULE, + .attach = dio200_attach, + .detach = dio200_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 00000000000..43160b9944b --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200.h @@ -0,0 +1,90 @@ +/* + 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 + +/* 200 series register area sizes */ +#define DIO200_IO_SIZE 0x20 +#define DIO200_PCIE_IO_SIZE 0x4000 + +/* + * Register region. + */ +enum dio200_regtype { no_regtype = 0, io_regtype, mmio_regtype }; +struct dio200_region { + union { + unsigned long iobase; /* I/O base address */ + unsigned char __iomem *membase; /* mapped MMIO base address */ + } u; + enum dio200_regtype regtype; +}; + +/* + * 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 + +/* + * Board descriptions. + */ + +struct dio200_layout { + 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 has_enhancements:1; /* has enhanced features */ +}; + +enum dio200_bustype { isa_bustype, pci_bustype }; + +struct dio200_board { + const char *name; + struct dio200_layout layout; + enum dio200_bustype bustype; + unsigned char mainbar; + unsigned char mainshift; + unsigned int mainsize; +}; + +/* + * Comedi device private data. + */ +struct dio200_private { + struct dio200_region io; /* Register region */ + int intr_sd; +}; + +int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq, + unsigned long req_irq_flags); + +void amplc_dio200_common_detach(struct comedi_device *dev); + +/* 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 00000000000..3edaa4028da --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200_common.c @@ -0,0 +1,1223 @@ +/* + 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_fc.h" +#include "8253.h" + +/* 8255 control register bits */ +#define CR_C_LO_IO 0x01 +#define CR_B_IO 0x02 +#define CR_B_MODE 0x04 +#define CR_C_HI_IO 0x08 +#define CR_A_IO 0x10 +#define CR_A_MODE(a) ((a)<<5) +#define CR_CW 0x80 + +/* 200 series registers */ +#define DIO200_IO_SIZE 0x20 +#define DIO200_PCIE_IO_SIZE 0x4000 +#define DIO200_XCLK_SCE 0x18 /* Group X clock selection register */ +#define DIO200_YCLK_SCE 0x19 /* Group Y clock selection register */ +#define DIO200_ZCLK_SCE 0x1a /* Group Z clock selection register */ +#define DIO200_XGAT_SCE 0x1b /* Group X gate selection register */ +#define DIO200_YGAT_SCE 0x1c /* Group Y gate selection register */ +#define DIO200_ZGAT_SCE 0x1d /* Group Z gate selection register */ +#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_8254 { + unsigned int ofs; /* Counter base offset */ + unsigned int clk_sce_ofs; /* CLK_SCE base address */ + unsigned int gat_sce_ofs; /* GAT_SCE base address */ + int which; /* Bit 5 of CLK_SCE or GAT_SCE */ + unsigned int clock_src[3]; /* Current clock sources */ + unsigned int gate_src[3]; /* Current gate sources */ + spinlock_t spinlock; +}; + +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; + unsigned int stopcount; + bool active:1; +}; + +static inline const struct dio200_layout * +dio200_board_layout(const struct dio200_board *board) +{ + return &board->layout; +} + +static inline const struct dio200_layout * +dio200_dev_layout(struct comedi_device *dev) +{ + return dio200_board_layout(comedi_board(dev)); +} + +/* + * Read 8-bit register. + */ +static unsigned char dio200_read8(struct comedi_device *dev, + unsigned int offset) +{ + const struct dio200_board *thisboard = comedi_board(dev); + struct dio200_private *devpriv = dev->private; + + offset <<= thisboard->mainshift; + if (devpriv->io.regtype == io_regtype) + return inb(devpriv->io.u.iobase + offset); + else + return readb(devpriv->io.u.membase + offset); +} + +/* + * Write 8-bit register. + */ +static void dio200_write8(struct comedi_device *dev, unsigned int offset, + unsigned char val) +{ + const struct dio200_board *thisboard = comedi_board(dev); + struct dio200_private *devpriv = dev->private; + + offset <<= thisboard->mainshift; + if (devpriv->io.regtype == io_regtype) + outb(val, devpriv->io.u.iobase + offset); + else + writeb(val, devpriv->io.u.membase + offset); +} + +/* + * Read 32-bit register. + */ +static unsigned int dio200_read32(struct comedi_device *dev, + unsigned int offset) +{ + const struct dio200_board *thisboard = comedi_board(dev); + struct dio200_private *devpriv = dev->private; + + offset <<= thisboard->mainshift; + if (devpriv->io.regtype == io_regtype) + return inl(devpriv->io.u.iobase + offset); + else + return readl(devpriv->io.u.membase + offset); +} + +/* + * Write 32-bit register. + */ +static void dio200_write32(struct comedi_device *dev, unsigned int offset, + unsigned int val) +{ + const struct dio200_board *thisboard = comedi_board(dev); + struct dio200_private *devpriv = dev->private; + + offset <<= thisboard->mainshift; + if (devpriv->io.regtype == io_regtype) + outl(val, devpriv->io.u.iobase + offset); + else + writel(val, devpriv->io.u.membase + offset); +} + +/* + * 'insn_bits' function for an 'INTERRUPT' subdevice. + */ +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_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_intr *subpriv = s->private; + + if (layout->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; +} + +/* + * Called to stop acquisition for an 'INTERRUPT' subdevice. + */ +static void dio200_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_intr *subpriv = s->private; + + subpriv->active = false; + subpriv->enabled_isns = 0; + if (layout->has_int_sce) + dio200_write8(dev, subpriv->ofs, 0); +} + +/* + * Called to start acquisition for an 'INTERRUPT' subdevice. + */ +static int dio200_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int n; + unsigned isn_bits; + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_intr *subpriv = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + int retval = 0; + + if (cmd->stop_src == TRIG_COUNT && subpriv->stopcount == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + subpriv->active = false; + retval = 1; + } else { + /* 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 (layout->has_int_sce) + dio200_write8(dev, subpriv->ofs, isn_bits); + } + + return retval; +} + +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; + int event = 0; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&subpriv->spinlock, flags); + s->async->inttrig = NULL; + if (subpriv->active) + event = dio200_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 1; +} + +static void dio200_read_scan_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int triggered) +{ + struct dio200_subdev_intr *subpriv = s->private; + 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); + } + /* Write the scan to the buffer. */ + if (comedi_buf_put(s, val)) { + s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); + } else { + /* Error! Stop acquisition. */ + dio200_stop_intr(dev, s); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; + comedi_error(dev, "buffer overflow"); + } + + /* Check for end of acquisition. */ + if (cmd->stop_src == TRIG_COUNT) { + if (subpriv->stopcount > 0) { + subpriv->stopcount--; + if (subpriv->stopcount == 0) { + s->async->events |= COMEDI_CB_EOA; + dio200_stop_intr(dev, s); + } + } + } +} + +/* + * This is called from the interrupt service routine to handle a read + * scan on an 'INTERRUPT' subdevice. + */ +static int dio200_handle_read_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_intr *subpriv = s->private; + unsigned triggered; + unsigned intstat; + unsigned cur_enabled; + unsigned int oldevents; + unsigned long flags; + + triggered = 0; + + spin_lock_irqsave(&subpriv->spinlock, flags); + oldevents = s->async->events; + if (layout->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 (layout->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); + + if (oldevents != s->async->events) + comedi_event(dev, s); + + return (triggered != 0); +} + +/* + * 'cancel' function for an 'INTERRUPT' subdevice. + */ +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; +} + +/* + * 'do_cmdtest' function for an 'INTERRUPT' subdevice. + */ +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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* any count allowed */ + break; + case TRIG_NONE: + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* if (err) return 4; */ + + return 0; +} + +/* + * 'do_cmd' function for an 'INTERRUPT' subdevice. + */ +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; + int event = 0; + + spin_lock_irqsave(&subpriv->spinlock, flags); + subpriv->active = true; + + /* Set up end of acquisition. */ + if (cmd->stop_src == TRIG_COUNT) + subpriv->stopcount = cmd->stop_arg; + else /* TRIG_NONE */ + subpriv->stopcount = 0; + + if (cmd->start_src == TRIG_INT) + s->async->inttrig = dio200_inttrig_start_intr; + else /* TRIG_NOW */ + event = dio200_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 0; +} + +/* + * This function initializes an 'INTERRUPT' subdevice. + */ +static int +dio200_subdev_intr_init(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int offset, unsigned valid_isns) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + 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 (layout->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; + if (layout->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; +} + +/* + * Interrupt service routine. + */ +static irqreturn_t dio200_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct dio200_private *devpriv = dev->private; + struct comedi_subdevice *s; + int handled; + + if (!dev->attached) + return IRQ_NONE; + + if (devpriv->intr_sd >= 0) { + s = &dev->subdevices[devpriv->intr_sd]; + handled = dio200_handle_read_intr(dev, s); + } else { + handled = 0; + } + + return IRQ_RETVAL(handled); +} + +/* + * Read an '8254' counter subdevice channel. + */ +static unsigned int +dio200_subdev_8254_read_chan(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int chan) +{ + struct dio200_subdev_8254 *subpriv = s->private; + unsigned int val; + + /* latch counter */ + val = chan << 6; + dio200_write8(dev, subpriv->ofs + i8254_control_reg, val); + /* read lsb, msb */ + val = dio200_read8(dev, subpriv->ofs + chan); + val += dio200_read8(dev, subpriv->ofs + chan) << 8; + return val; +} + +/* + * Write an '8254' subdevice channel. + */ +static void +dio200_subdev_8254_write_chan(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int chan, + unsigned int count) +{ + struct dio200_subdev_8254 *subpriv = s->private; + + /* write lsb, msb */ + dio200_write8(dev, subpriv->ofs + chan, count & 0xff); + dio200_write8(dev, subpriv->ofs + chan, (count >> 8) & 0xff); +} + +/* + * Set mode of an '8254' subdevice channel. + */ +static void +dio200_subdev_8254_set_mode(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int chan, + unsigned int mode) +{ + struct dio200_subdev_8254 *subpriv = s->private; + unsigned int byte; + + byte = chan << 6; + byte |= 0x30; /* access order: lsb, msb */ + byte |= (mode & 0xf); /* counter mode and BCD|binary */ + dio200_write8(dev, subpriv->ofs + i8254_control_reg, byte); +} + +/* + * Read status byte of an '8254' counter subdevice channel. + */ +static unsigned int +dio200_subdev_8254_status(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int chan) +{ + struct dio200_subdev_8254 *subpriv = s->private; + + /* latch status */ + dio200_write8(dev, subpriv->ofs + i8254_control_reg, + 0xe0 | (2 << chan)); + /* read status */ + return dio200_read8(dev, subpriv->ofs + chan); +} + +/* + * Handle 'insn_read' for an '8254' counter subdevice. + */ +static int +dio200_subdev_8254_read(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dio200_subdev_8254 *subpriv = s->private; + int chan = CR_CHAN(insn->chanspec); + unsigned int n; + unsigned long flags; + + for (n = 0; n < insn->n; n++) { + spin_lock_irqsave(&subpriv->spinlock, flags); + data[n] = dio200_subdev_8254_read_chan(dev, s, chan); + spin_unlock_irqrestore(&subpriv->spinlock, flags); + } + return insn->n; +} + +/* + * Handle 'insn_write' for an '8254' counter subdevice. + */ +static int +dio200_subdev_8254_write(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dio200_subdev_8254 *subpriv = s->private; + int chan = CR_CHAN(insn->chanspec); + unsigned int n; + unsigned long flags; + + for (n = 0; n < insn->n; n++) { + spin_lock_irqsave(&subpriv->spinlock, flags); + dio200_subdev_8254_write_chan(dev, s, chan, data[n]); + spin_unlock_irqrestore(&subpriv->spinlock, flags); + } + return insn->n; +} + +/* + * Set gate source for an '8254' counter subdevice channel. + */ +static int +dio200_subdev_8254_set_gate_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int counter_number, + unsigned int gate_src) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_8254 *subpriv = s->private; + unsigned char byte; + + if (!layout->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + if (gate_src > (layout->has_enhancements ? 31 : 7)) + return -1; + + subpriv->gate_src[counter_number] = gate_src; + byte = gat_sce(subpriv->which, counter_number, gate_src); + dio200_write8(dev, subpriv->gat_sce_ofs, byte); + + return 0; +} + +/* + * Get gate source for an '8254' counter subdevice channel. + */ +static int +dio200_subdev_8254_get_gate_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int counter_number) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_8254 *subpriv = s->private; + + if (!layout->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + + return subpriv->gate_src[counter_number]; +} + +/* + * Set clock source for an '8254' counter subdevice channel. + */ +static int +dio200_subdev_8254_set_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int counter_number, + unsigned int clock_src) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_8254 *subpriv = s->private; + unsigned char byte; + + if (!layout->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + if (clock_src > (layout->has_enhancements ? 31 : 7)) + return -1; + + subpriv->clock_src[counter_number] = clock_src; + byte = clk_sce(subpriv->which, counter_number, clock_src); + dio200_write8(dev, subpriv->clk_sce_ofs, byte); + + return 0; +} + +/* + * Get clock source for an '8254' counter subdevice channel. + */ +static int +dio200_subdev_8254_get_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int counter_number, + unsigned int *period_ns) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_8254 *subpriv = s->private; + unsigned clock_src; + + if (!layout->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + + clock_src = subpriv->clock_src[counter_number]; + *period_ns = clock_period[clock_src]; + return clock_src; +} + +/* + * Handle 'insn_config' for an '8254' counter subdevice. + */ +static int +dio200_subdev_8254_config(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dio200_subdev_8254 *subpriv = s->private; + int ret = 0; + int chan = CR_CHAN(insn->chanspec); + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + if (data[1] > (I8254_MODE5 | I8254_BCD)) + ret = -EINVAL; + else + dio200_subdev_8254_set_mode(dev, s, chan, data[1]); + break; + case INSN_CONFIG_8254_READ_STATUS: + data[1] = dio200_subdev_8254_status(dev, s, chan); + break; + case INSN_CONFIG_SET_GATE_SRC: + ret = dio200_subdev_8254_set_gate_src(dev, s, chan, data[2]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_GET_GATE_SRC: + ret = dio200_subdev_8254_get_gate_src(dev, s, chan); + if (ret < 0) { + ret = -EINVAL; + break; + } + data[2] = ret; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + ret = dio200_subdev_8254_set_clock_src(dev, s, chan, data[1]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ret = dio200_subdev_8254_get_clock_src(dev, s, chan, &data[2]); + if (ret < 0) { + ret = -EINVAL; + break; + } + data[1] = ret; + break; + default: + ret = -EINVAL; + break; + } + spin_unlock_irqrestore(&subpriv->spinlock, flags); + return ret < 0 ? ret : insn->n; +} + +/* + * This function initializes an '8254' counter subdevice. + */ +static int +dio200_subdev_8254_init(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int offset) +{ + const struct dio200_layout *layout = dio200_dev_layout(dev); + struct dio200_subdev_8254 *subpriv; + unsigned int chan; + + subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); + if (!subpriv) + return -ENOMEM; + + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 3; + s->maxdata = 0xFFFF; + s->insn_read = dio200_subdev_8254_read; + s->insn_write = dio200_subdev_8254_write; + s->insn_config = dio200_subdev_8254_config; + + spin_lock_init(&subpriv->spinlock); + subpriv->ofs = offset; + if (layout->has_clk_gat_sce) { + /* Derive CLK_SCE and GAT_SCE register offsets from + * 8254 offset. */ + subpriv->clk_sce_ofs = DIO200_XCLK_SCE + (offset >> 3); + subpriv->gat_sce_ofs = DIO200_XGAT_SCE + (offset >> 3); + subpriv->which = (offset >> 2) & 1; + } + + /* Initialize channels. */ + for (chan = 0; chan < 3; chan++) { + dio200_subdev_8254_set_mode(dev, s, chan, + I8254_MODE0 | I8254_BINARY); + if (layout->has_clk_gat_sce) { + /* 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; +} + +/* + * This function sets I/O directions for an '8255' DIO subdevice. + */ +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 = CR_CW; + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= CR_A_IO; + if (!(s->io_bits & 0x00ff00)) + config |= CR_B_IO; + if (!(s->io_bits & 0x0f0000)) + config |= CR_C_LO_IO; + if (!(s->io_bits & 0xf00000)) + config |= CR_C_HI_IO; + dio200_write8(dev, subpriv->ofs + 3, 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, s->state & 0xff); + if (mask & 0xff00) + dio200_write8(dev, subpriv->ofs + 1, + (s->state >> 8) & 0xff); + if (mask & 0xff0000) + dio200_write8(dev, subpriv->ofs + 2, + (s->state >> 16) & 0xff); + } + + val = dio200_read8(dev, subpriv->ofs); + val |= dio200_read8(dev, subpriv->ofs + 1) << 8; + val |= dio200_read8(dev, subpriv->ofs + 2) << 16; + + data[1] = val; + + return insn->n; +} + +/* + * Handle 'insn_config' for an '8255' DIO subdevice. + */ +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; +} + +/* + * This function initializes an '8255' DIO subdevice. + * + * offset is the offset to the 8255 chip. + */ +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; +} + +/* + * Handle 'insn_read' for a timer subdevice. + */ +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; +} + +/* + * Reset timer subdevice. + */ +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); +} + +/* + * Get timer subdevice clock source and period. + */ +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; +} + +/* + * Set timer subdevice clock source. + */ +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; +} + +/* + * Handle 'insn_config' for a timer subdevice. + */ +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; +} + +/* + * This function initializes a timer subdevice. + * + * Uses the timestamp timer registers. There is only one timestamp timer. + */ +static int dio200_subdev_timer_init(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + 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; + return 0; +} + +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 *thisboard = comedi_board(dev); + struct dio200_private *devpriv = dev->private; + const struct dio200_layout *layout = dio200_board_layout(thisboard); + struct comedi_subdevice *s; + int sdx; + unsigned int n; + int ret; + + devpriv->intr_sd = -1; + + ret = comedi_alloc_subdevices(dev, layout->n_subdevs); + if (ret) + return ret; + + for (n = 0; n < dev->n_subdevices; n++) { + s = &dev->subdevices[n]; + switch (layout->sdtype[n]) { + case sd_8254: + /* counter subdevice (8254) */ + ret = dio200_subdev_8254_init(dev, s, + layout->sdinfo[n]); + if (ret < 0) + return ret; + break; + case sd_8255: + /* digital i/o subdevice (8255) */ + ret = dio200_subdev_8255_init(dev, s, + layout->sdinfo[n]); + if (ret < 0) + return ret; + break; + case sd_intr: + /* 'INTERRUPT' subdevice */ + if (irq) { + ret = dio200_subdev_intr_init(dev, s, + DIO200_INT_SCE, + layout->sdinfo[n] + ); + if (ret < 0) + return ret; + devpriv->intr_sd = n; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + break; + case sd_timer: + ret = dio200_subdev_timer_init(dev, s); + if (ret < 0) + return ret; + break; + default: + s->type = COMEDI_SUBD_UNUSED; + break; + } + } + sdx = devpriv->intr_sd; + if (sdx >= 0 && sdx < dev->n_subdevices) + dev->read_subdev = &dev->subdevices[sdx]; + if (irq) { + 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); + +void amplc_dio200_common_detach(struct comedi_device *dev) +{ + const struct dio200_board *thisboard = comedi_board(dev); + struct dio200_private *devpriv = dev->private; + + if (!thisboard || !devpriv) + return; + if (dev->irq) + free_irq(dev->irq, dev); +} +EXPORT_SYMBOL_GPL(amplc_dio200_common_detach); + +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 00000000000..e0367380b37 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200_pci.c @@ -0,0 +1,480 @@ +/* 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/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "amplc_dio200.h" + +/* PCI IDs */ +#define PCI_DEVICE_ID_AMPLICON_PCI272 0x000a +#define PCI_DEVICE_ID_AMPLICON_PCI215 0x000b +#define PCI_DEVICE_ID_AMPLICON_PCIE236 0x0011 +#define PCI_DEVICE_ID_AMPLICON_PCIE215 0x0012 +#define PCI_DEVICE_ID_AMPLICON_PCIE296 0x0014 + +/* + * 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", + .bustype = pci_bustype, + .mainbar = 2, + .mainsize = DIO200_IO_SIZE, + .layout = { + .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", + .bustype = pci_bustype, + .mainbar = 2, + .mainsize = DIO200_IO_SIZE, + .layout = { + .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", + .bustype = pci_bustype, + .mainbar = 1, + .mainshift = 3, + .mainsize = DIO200_PCIE_IO_SIZE, + .layout = { + .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, + .has_enhancements = true, + }, + }, + [pcie236_model] = { + .name = "pcie236", + .bustype = pci_bustype, + .mainbar = 1, + .mainshift = 3, + .mainsize = DIO200_PCIE_IO_SIZE, + .layout = { + .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, + .has_enhancements = true, + }, + }, + [pcie296_model] = { + .name = "pcie296", + .bustype = pci_bustype, + .mainbar = 1, + .mainshift = 3, + .mainsize = DIO200_PCIE_IO_SIZE, + .layout = { + .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, + .has_enhancements = 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 *thisboard = NULL; + struct dio200_private *devpriv; + unsigned int bar; + int ret; + + if (context_model < ARRAY_SIZE(dio200_pci_boards)) + thisboard = &dio200_pci_boards[context_model]; + if (!thisboard) + return -EINVAL; + dev->board_ptr = thisboard; + dev->board_name = thisboard->name; + + dev_info(dev->class_dev, "%s: attach pci %s (%s)\n", + dev->driver->driver_name, 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; + + bar = thisboard->mainbar; + if (pci_resource_len(pci_dev, bar) < thisboard->mainsize) { + dev_err(dev->class_dev, "error! PCI region size too small!\n"); + return -EINVAL; + } + if (pci_resource_flags(pci_dev, bar) & IORESOURCE_MEM) { + devpriv->io.u.membase = pci_ioremap_bar(pci_dev, bar); + if (!devpriv->io.u.membase) { + dev_err(dev->class_dev, + "error! cannot remap registers\n"); + return -ENOMEM; + } + devpriv->io.regtype = mmio_regtype; + } else { + devpriv->io.u.iobase = pci_resource_start(pci_dev, bar); + devpriv->io.regtype = io_regtype; + } + switch (context_model) { + case pcie215_model: + case pcie236_model: + case pcie296_model: + ret = dio200_pcie_board_setup(dev); + if (ret < 0) + return ret; + break; + default: + break; + } + return amplc_dio200_common_attach(dev, pci_dev->irq, IRQF_SHARED); +} + +static void dio200_pci_detach(struct comedi_device *dev) +{ + const struct dio200_board *thisboard = comedi_board(dev); + struct dio200_private *devpriv = dev->private; + + if (!thisboard || !devpriv) + return; + amplc_dio200_common_detach(dev); + if (devpriv->io.regtype == mmio_regtype) + iounmap(devpriv->io.u.membase); + comedi_pci_disable(dev); +} + +static struct comedi_driver dio200_pci_comedi_driver = { + .driver_name = "amplc_dio200_pci", + .module = THIS_MODULE, + .auto_attach = dio200_pci_auto_attach, + .detach = dio200_pci_detach, +}; + +static const struct pci_device_id dio200_pci_table[] = { + { + PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI215), + pci215_model + }, { + PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI272), + pci272_model + }, { + PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE236), + pcie236_model + }, { + PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE215), + pcie215_model + }, { + PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE296), + 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 00000000000..c9a96ad0055 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pc236.c @@ -0,0 +1,572 @@ +/* + comedi/drivers/amplc_pc236.c + Driver for Amplicon PC36AT and PCI236 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, PCI236 +Author: Ian Abbott <abbotti@mev.co.uk> +Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236) +Updated: Wed, 01 Apr 2009 15:41:25 +0100 +Status: works + +Configuration options - PC36AT: + [0] - I/O port base address + [1] - IRQ (optional) + +Configuration options - PCI236: + [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. + +The PC36AT ISA board and PCI236 PCI board have 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 <linux/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "8255.h" +#include "plx9052.h" + +#define PC236_DRIVER_NAME "amplc_pc236" + +#define DO_ISA IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) +#define DO_PCI IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) + +/* PCI236 PCI configuration register information */ +#define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009 +#define PCI_DEVICE_ID_INVALID 0xffff + +/* PC36AT / PCI236 registers */ + +#define PC236_IO_SIZE 4 +#define PC236_LCR_IO_SIZE 128 + +/* 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) + +/* + * Board descriptions for Amplicon PC36AT and PCI236. + */ + +enum pc236_bustype { isa_bustype, pci_bustype }; +enum pc236_model { pc36at_model, pci236_model, anypci_model }; + +struct pc236_board { + const char *name; + unsigned short devid; + enum pc236_bustype bustype; + enum pc236_model model; +}; +static const struct pc236_board pc236_boards[] = { +#if DO_ISA + { + .name = "pc36at", + .bustype = isa_bustype, + .model = pc36at_model, + }, +#endif +#if DO_PCI + { + .name = "pci236", + .devid = PCI_DEVICE_ID_AMPLICON_PCI236, + .bustype = pci_bustype, + .model = pci236_model, + }, + { + .name = PC236_DRIVER_NAME, + .devid = PCI_DEVICE_ID_INVALID, + .bustype = pci_bustype, + .model = anypci_model, /* wildcard */ + }, +#endif +}; + +/* 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 pc236_private { + unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */ + int enable_irq; +}; + +/* test if ISA supported and this is an ISA board */ +static inline bool is_isa_board(const struct pc236_board *board) +{ + return DO_ISA && board->bustype == isa_bustype; +} + +/* test if PCI supported and this is a PCI board */ +static inline bool is_pci_board(const struct pc236_board *board) +{ + return DO_PCI && board->bustype == pci_bustype; +} + +/* + * This function looks for a board matching the supplied PCI device. + */ +static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) + if (is_pci_board(&pc236_boards[i]) && + pci_dev->device == pc236_boards[i].devid) + return &pc236_boards[i]; + return NULL; +} + +/* + * This function looks for a PCI device matching the requested board name, + * bus and slot. + */ +static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct pc236_board *thisboard = comedi_board(dev); + struct pci_dev *pci_dev = NULL; + int bus = it->options[0]; + int slot = it->options[1]; + + for_each_pci_dev(pci_dev) { + if (bus || slot) { + if (bus != pci_dev->bus->number || + slot != PCI_SLOT(pci_dev->devfn)) + continue; + } + if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON) + continue; + + if (thisboard->model == anypci_model) { + /* Wildcard board matches any supported PCI board. */ + const struct pc236_board *foundboard; + + foundboard = pc236_find_pci_board(pci_dev); + if (foundboard == NULL) + continue; + /* Replace wildcard board_ptr. */ + dev->board_ptr = foundboard; + } else { + /* Match specific model name. */ + if (pci_dev->device != thisboard->devid) + continue; + } + return pci_dev; + } + dev_err(dev->class_dev, + "No supported board found! (req. bus %d, slot %d)\n", + bus, slot); + return NULL; +} + +/* + * This function is called to mark the interrupt as disabled (no command + * configured on subdevice 1) and to physically disable the interrupt + * (not possible on the PC36AT, except by removing the IRQ jumper!). + */ +static void pc236_intr_disable(struct comedi_device *dev) +{ + const struct pc236_board *thisboard = comedi_board(dev); + struct pc236_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->enable_irq = 0; + if (is_pci_board(thisboard)) + outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR); + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +/* + * This function is called to mark the interrupt as enabled (a command + * configured on subdevice 1) and to physically enable the interrupt + * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!). + */ +static void pc236_intr_enable(struct comedi_device *dev) +{ + const struct pc236_board *thisboard = comedi_board(dev); + struct pc236_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->enable_irq = 1; + if (is_pci_board(thisboard)) + outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR); + 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 0 if the interrupt should be ignored. + */ +static int pc236_intr_check(struct comedi_device *dev) +{ + const struct pc236_board *thisboard = comedi_board(dev); + struct pc236_private *devpriv = dev->private; + int retval = 0; + unsigned long flags; + unsigned int intcsr; + + spin_lock_irqsave(&dev->spinlock, flags); + if (devpriv->enable_irq) { + retval = 1; + if (is_pci_board(thisboard)) { + intcsr = inl(devpriv->lcr_iobase + PLX9052_INTCSR); + if (!(intcsr & PLX9052_INTCSR_LI1STAT)) { + retval = 0; + } else { + /* Clear interrupt and keep it enabled. */ + outl(PCI236_INTR_ENABLE, + devpriv->lcr_iobase + PLX9052_INTCSR); + } + } + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + return retval; +} + +/* + * Input from subdevice 1. + * Copied from the comedi_parport driver. + */ +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; +} + +/* + * Subdevice 1 command test. + * Copied from the comedi_parport driver. + */ +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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check it arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + return 0; +} + +/* + * Subdevice 1 command. + */ +static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + pc236_intr_enable(dev); + + return 0; +} + +/* + * Subdevice 1 cancel command. + */ +static int pc236_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pc236_intr_disable(dev); + + return 0; +} + +/* + * Interrupt service routine. + * Based on the comedi_parport driver. + */ +static irqreturn_t pc236_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + int handled; + + handled = pc236_intr_check(dev); + if (dev->attached && handled) { + comedi_buf_put(s, 0); + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + comedi_event(dev, s); + } + return IRQ_RETVAL(handled); +} + +static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase, + unsigned int irq, unsigned long req_irq_flags) +{ + const struct pc236_board *thisboard = comedi_board(dev); + struct comedi_subdevice *s; + int ret; + + dev->board_name = thisboard->name; + 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, iobase); + if (ret) + return ret; + + s = &dev->subdevices[1]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_UNUSED; + pc236_intr_disable(dev); + if (irq) { + if (request_irq(irq, pc236_interrupt, req_irq_flags, + PC236_DRIVER_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; +} + +static int pc236_pci_common_attach(struct comedi_device *dev, + struct pci_dev *pci_dev) +{ + struct pc236_private *devpriv = dev->private; + unsigned long iobase; + int ret; + + comedi_set_hw_dev(dev, &pci_dev->dev); + + 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 pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED); +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pc236_board *thisboard = comedi_board(dev); + struct pc236_private *devpriv; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* Process options according to bus type. */ + if (is_isa_board(thisboard)) { + ret = comedi_request_region(dev, it->options[0], PC236_IO_SIZE); + if (ret) + return ret; + + return pc236_common_attach(dev, dev->iobase, it->options[1], 0); + } else if (is_pci_board(thisboard)) { + struct pci_dev *pci_dev; + + pci_dev = pc236_find_pci_dev(dev, it); + if (!pci_dev) + return -EIO; + return pc236_pci_common_attach(dev, pci_dev); + } else { + dev_err(dev->class_dev, PC236_DRIVER_NAME + ": BUG! cannot determine board type!\n"); + return -EINVAL; + } +} + +/* + * The auto_attach hook is called at PCI probe time via + * comedi_pci_auto_config(). dev->board_ptr is NULL on entry. + * There should be a board entry matching the supplied PCI device. + */ +static int pc236_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pci_dev = comedi_to_pci_dev(dev); + struct pc236_private *devpriv; + + if (!DO_PCI) + return -EINVAL; + + dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n", + pci_name(pci_dev)); + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + dev->board_ptr = pc236_find_pci_board(pci_dev); + if (dev->board_ptr == NULL) { + dev_err(dev->class_dev, "BUG! cannot determine board type!\n"); + return -EINVAL; + } + /* + * Need to 'get' the PCI device to match the 'put' in pc236_detach(). + * TODO: Remove the pci_dev_get() and matching pci_dev_put() once + * support for manual attachment of PCI devices via pc236_attach() + * has been removed. + */ + pci_dev_get(pci_dev); + return pc236_pci_common_attach(dev, pci_dev); +} + +static void pc236_detach(struct comedi_device *dev) +{ + const struct pc236_board *thisboard = comedi_board(dev); + + if (!thisboard) + return; + if (dev->iobase) + pc236_intr_disable(dev); + if (is_isa_board(thisboard)) { + comedi_legacy_detach(dev); + } else if (is_pci_board(thisboard)) { + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(dev); + if (pcidev) + pci_dev_put(pcidev); + } +} + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static struct comedi_driver amplc_pc236_driver = { + .driver_name = PC236_DRIVER_NAME, + .module = THIS_MODULE, + .attach = pc236_attach, + .auto_attach = pc236_auto_attach, + .detach = pc236_detach, + .board_name = &pc236_boards[0].name, + .offset = sizeof(struct pc236_board), + .num_names = ARRAY_SIZE(pc236_boards), +}; + +#if DO_PCI +static const struct pci_device_id pc236_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, pc236_pci_table); + +static int amplc_pc236_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &lc_pc236_driver, + id->driver_data); +} + +static struct pci_driver amplc_pc236_pci_driver = { + .name = PC236_DRIVER_NAME, + .id_table = pc236_pci_table, + .probe = &lc_pc236_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; + +module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver); +#else +module_comedi_driver(amplc_pc236_driver); +#endif + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +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 00000000000..7c10d28d278 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pc263.c @@ -0,0 +1,114 @@ +/* + 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" + +#define PC263_DRIVER_NAME "amplc_pc263" + +/* PC263 registers */ +#define PC263_IO_SIZE 2 + +/* + * 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], PC263_IO_SIZE); + 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_READABLE | 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 = PC263_DRIVER_NAME, + .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 00000000000..339c47c1eb9 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pci224.c @@ -0,0 +1,1372 @@ +/* + 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 or pci224), + PCI234 (amplc_pci224 or pci234) +Updated: Wed, 22 Oct 2008 12:25:08 +0100 +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 - PCI224: + [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. + [2] - Select available ranges according to jumper LK1. All channels + are set to the same range: + 0=Jumper position 1-2 (factory default), 4 software-selectable + internal voltage references, giving 4 bipolar and 4 unipolar + ranges: + [-10V,+10V], [-5V,+5V], [-2.5V,+2.5V], [-1.25V,+1.25V], + [0,+10V], [0,+5V], [0,+2.5V], [0,1.25V]. + 1=Jumper position 2-3, 1 external voltage reference, giving + 1 bipolar and 1 unipolar range: + [-Vext,+Vext], [0,+Vext]. + +Configuration options - PCI234: + [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. + [2] - Select internal or external voltage reference according to + jumper LK1. This affects all channels: + 0=Jumper position 1-2 (factory default), Vref=5V internal. + 1=Jumper position 2-3, Vref=Vext external. + [3] - Select channel 0 range according to jumper LK2: + 0=Jumper position 2-3 (factory default), range [-2*Vref,+2*Vref] + (10V bipolar when options[2]=0). + 1=Jumper position 1-2, range [-Vref,+Vref] + (5V bipolar when options[2]=0). + [4] - Select channel 1 range according to jumper LK3: cf. options[3]. + [5] - Select channel 2 range according to jumper LK4: cf. options[3]. + [6] - Select channel 3 range according to jumper LK5: cf. options[3]. + +Passing a zero for an option is the same as leaving it unspecified. + +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/pci.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "8253.h" + +#define DRIVER_NAME "amplc_pci224" + +/* + * PCI IDs. + */ +#define PCI_DEVICE_ID_AMPLICON_PCI224 0x0007 +#define PCI_DEVICE_ID_AMPLICON_PCI234 0x0008 +#define PCI_DEVICE_ID_INVALID 0xffff + +/* + * PCI224/234 i/o space 1 (PCIBAR2) registers. + */ +#define PCI224_IO1_SIZE 0x20 /* Size of i/o space 1 (8-bit registers) */ +#define PCI224_Z2_CT0 0x14 /* 82C54 counter/timer 0 */ +#define PCI224_Z2_CT1 0x15 /* 82C54 counter/timer 1 */ +#define PCI224_Z2_CT2 0x16 /* 82C54 counter/timer 2 */ +#define PCI224_Z2_CTC 0x17 /* 82C54 counter/timer control word */ +#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_IO2_SIZE 0x10 /* Size of i/o space 2 (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 software selectable internal ranges for PCI224 (option[2] == 0). */ +static const struct comedi_lrange range_pci224_internal = { + 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 unsigned short hwrange_pci224_internal[8] = { + 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, +}; + +/* The software selectable external ranges for PCI224 (option[2] == 1). */ +static const struct comedi_lrange range_pci224_external = { + 2, { + RANGE_ext(-1, 1), /* bipolar [-Vref,+Vref] */ + RANGE_ext(0, 1) /* unipolar [0,+Vref] */ + } +}; + +static const unsigned short hwrange_pci224_external[2] = { + PCI224_DACCON_POLAR_BI, + PCI224_DACCON_POLAR_UNI, +}; + +/* The hardware selectable Vref*2 external range for PCI234 + * (option[2] == 1, option[3+n] == 0). */ +static const struct comedi_lrange range_pci234_ext2 = { + 1, { + RANGE_ext(-2, 2) + } +}; + +/* The hardware selectable Vref external range for PCI234 + * (option[2] == 1, option[3+n] == 1). */ +static const struct comedi_lrange range_pci234_ext = { + 1, { + RANGE_ext(-1, 1) + } +}; + +/* This serves for all the PCI234 ranges. */ +static const unsigned short hwrange_pci234[1] = { + PCI224_DACCON_POLAR_BI, /* bipolar - hardware ignores it! */ +}; + +/* + * Board descriptions. + */ + +enum pci224_model { any_model, pci224_model, pci234_model }; + +struct pci224_board { + const char *name; + unsigned short devid; + enum pci224_model model; + unsigned int ao_chans; + unsigned int ao_bits; +}; + +static const struct pci224_board pci224_boards[] = { + { + .name = "pci224", + .devid = PCI_DEVICE_ID_AMPLICON_PCI224, + .model = pci224_model, + .ao_chans = 16, + .ao_bits = 12, + }, + { + .name = "pci234", + .devid = PCI_DEVICE_ID_AMPLICON_PCI234, + .model = pci234_model, + .ao_chans = 4, + .ao_bits = 16, + }, + { + .name = DRIVER_NAME, + .devid = PCI_DEVICE_ID_INVALID, + .model = any_model, /* wildcard */ + }, +}; + +/* 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 pci224_private { + const unsigned short *hwrange; + unsigned long iobase1; + unsigned long state; + spinlock_t ao_spinlock; + unsigned int *ao_readback; + unsigned short *ao_scan_vals; + unsigned char *ao_scan_order; + int intr_cpuid; + short intr_running; + unsigned short daccon; + unsigned int cached_div1; + unsigned int cached_div2; + unsigned int ao_stop_count; + 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 = comedi_board(dev); + struct pci224_private *devpriv = dev->private; + unsigned short mangled; + + /* Store unmangled data for readback. */ + devpriv->ao_readback[chan] = data; + /* Enable the channel. */ + outw(1 << chan, dev->iobase + PCI224_DACCEN); + /* Set range and reset FIFO. */ + devpriv->daccon = COMBINE(devpriv->daccon, devpriv->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); +} + +/* + * 'insn_write' function for AO subdevice. + */ +static int +pci224_ao_insn_write(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan, range; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* 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++) + pci224_ao_set_data(dev, chan, range, data[i]); + + return i; +} + +/* + * 'insn_read' function for AO subdevice. + * + * N.B. The value read will not be valid if the DAC channel has + * never been written successfully since the device was attached + * or since the channel has been used by an AO streaming write + * command. + */ +static int +pci224_ao_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci224_private *devpriv = dev->private; + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + + return i; +} + +/* + * 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); + if (cmd->stop_src == TRIG_COUNT && devpriv->ao_stop_count == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + cfc_handle_events(dev, s); + } else { + /* 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 bytes_per_scan = cfc_bytes_per_scan(s); + unsigned int num_scans; + unsigned int room; + unsigned short dacstat; + unsigned int i, n; + + /* Determine number of scans available in buffer. */ + num_scans = comedi_buf_read_n_available(s) / bytes_per_scan; + if (cmd->stop_src == TRIG_COUNT) { + /* Fixed number of scans. */ + if (num_scans > devpriv->ao_stop_count) + num_scans = devpriv->ao_stop_count; + + } + + /* 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 && devpriv->ao_stop_count == 0) { + /* FIFO empty at end of counted acquisition. */ + s->async->events |= COMEDI_CB_EOA; + cfc_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++) { + cfc_read_array_from_buffer(s, &devpriv->ao_scan_vals[0], + bytes_per_scan); + 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) { + devpriv->ao_stop_count -= num_scans; + if (devpriv->ao_stop_count == 0) { + /* + * 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); + } + + cfc_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) +{ + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int chan_mask = 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]); + + 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 (range != range0) { + dev_dbg(dev->class_dev, + "%s: entries in chanlist must all have the same range index\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) +{ + struct pci224_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_EXT | TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if ((cmd->start_arg & ~CR_FLAGS_MASK) != 0) { + 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) != 0) { + 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 |= cfc_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 |= cfc_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) != 0) { + 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)) != 0) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & + ~(CR_EDGE | CR_INVERT)); + err |= -EINVAL; + } + break; + } + + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* Any count allowed. */ + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if ((cmd->stop_arg & ~CR_FLAGS_MASK) != 0) { + 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) != 0) { + cmd->stop_arg = COMBINE(cmd->stop_arg, 0, + CR_FLAGS_MASK & ~CR_EDGE); + } + break; + case TRIG_NONE: + err |= cfc_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. */ + i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, + &devpriv->cached_div1, + &devpriv->cached_div2, + &arg, cmd->flags); + err |= cfc_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; + unsigned long timer_base = devpriv->iobase1 + PCI224_Z2_CT0; + + /* + * 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); + /* Load Z2-2 mode (2) and counter (div1). */ + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + i8254_write(timer_base, 0, 2, devpriv->cached_div1); + /* Z2-0 is clocked from Z2-2's output. */ + outb(CLK_CONFIG(0, CLK_OUTNM1), devpriv->iobase1 + PCI224_ZCLK_SCE); + /* Load Z2-0 mode (2) and counter (div2). */ + i8254_set_mode(timer_base, 0, 0, I8254_MODE2 | I8254_BINARY); + i8254_write(timer_base, 0, 0, devpriv->cached_div2); +} + +static int pci224_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + 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 == NULL || 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, + (devpriv-> + 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) + pci224_ao_start_pacer(dev, s); + + /* + * Sort out end of acquisition. + */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->ao_stop_count = cmd->stop_arg; + else /* TRIG_EXT | TRIG_NONE */ + devpriv->ao_stop_count = 0; + + 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 = comedi_board(dev); + struct pci224_private *devpriv = dev->private; + 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 ((devpriv->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 != 0) { + 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); +} + +/* + * This function looks for a board matching the supplied PCI device. + */ +static const struct pci224_board +*pci224_find_pci_board(struct pci_dev *pci_dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pci224_boards); i++) + if (pci_dev->device == pci224_boards[i].devid) + return &pci224_boards[i]; + return NULL; +} + +/* + * This function looks for a PCI device matching the requested board name, + * bus and slot. + */ +static struct pci_dev *pci224_find_pci_dev(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct pci224_board *thisboard = comedi_board(dev); + struct pci_dev *pci_dev = NULL; + int bus = it->options[0]; + int slot = it->options[1]; + + for_each_pci_dev(pci_dev) { + if (bus || slot) { + if (bus != pci_dev->bus->number || + slot != PCI_SLOT(pci_dev->devfn)) + continue; + } + if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON) + continue; + + if (thisboard->model == any_model) { + /* Match any supported model. */ + const struct pci224_board *board_ptr; + + board_ptr = pci224_find_pci_board(pci_dev); + if (board_ptr == NULL) + continue; + /* Change board_ptr to matched board. */ + dev->board_ptr = board_ptr; + } else { + /* Match specific model name. */ + if (thisboard->devid != pci_dev->device) + continue; + } + return pci_dev; + } + dev_err(dev->class_dev, + "No supported board found! (req. bus %d, slot %d)\n", + bus, slot); + return NULL; +} + +/* + * Common part of attach and auto_attach. + */ +static int pci224_attach_common(struct comedi_device *dev, + struct pci_dev *pci_dev, int *options) +{ + const struct pci224_board *thisboard = comedi_board(dev); + struct pci224_private *devpriv = dev->private; + struct comedi_subdevice *s; + unsigned int irq; + unsigned n; + int ret; + + comedi_set_hw_dev(dev, &pci_dev->dev); + + 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 readback buffer for AO channels. */ + devpriv->ao_readback = kmalloc(sizeof(devpriv->ao_readback[0]) * + thisboard->ao_chans, GFP_KERNEL); + if (!devpriv->ao_readback) + return -ENOMEM; + + + /* 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); + + 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->insn_write = &pci224_ao_insn_write; + s->insn_read = &pci224_ao_insn_read; + 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; + + /* Sort out channel range options. */ + if (thisboard->model == pci234_model) { + /* PCI234 range options. */ + const struct comedi_lrange **range_table_list; + + s->range_table_list = range_table_list = + kmalloc(sizeof(struct comedi_lrange *) * s->n_chan, + GFP_KERNEL); + if (!s->range_table_list) + return -ENOMEM; + + if (options) { + for (n = 2; n < 3 + s->n_chan; n++) { + if (options[n] < 0 || options[n] > 1) { + dev_warn(dev->class_dev, DRIVER_NAME + ": warning! bad options[%u]=%d\n", + n, options[n]); + } + } + } + for (n = 0; n < s->n_chan; n++) { + if (n < COMEDI_NDEVCONFOPTS - 3 && options && + options[3 + n] == 1) { + if (options[2] == 1) + range_table_list[n] = &range_pci234_ext; + else + range_table_list[n] = &range_bipolar5; + + } else { + if (options && options[2] == 1) { + range_table_list[n] = + &range_pci234_ext2; + } else { + range_table_list[n] = &range_bipolar10; + } + } + } + devpriv->hwrange = hwrange_pci234; + } else { + /* PCI224 range options. */ + if (options && options[2] == 1) { + s->range_table = &range_pci224_external; + devpriv->hwrange = hwrange_pci224_external; + } else { + if (options && options[2] != 0) { + dev_warn(dev->class_dev, DRIVER_NAME + ": warning! bad options[2]=%d\n", + options[2]); + } + s->range_table = &range_pci224_internal; + devpriv->hwrange = hwrange_pci224_internal; + } + } + + dev->board_name = thisboard->name; + + if (irq) { + ret = request_irq(irq, pci224_interrupt, IRQF_SHARED, + DRIVER_NAME, dev); + if (ret < 0) { + dev_err(dev->class_dev, + "error! unable to allocate irq %u\n", irq); + return ret; + } else { + dev->irq = irq; + } + } + + return 0; +} + +static int pci224_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pci224_private *devpriv; + struct pci_dev *pci_dev; + + dev_info(dev->class_dev, DRIVER_NAME ": attach\n"); + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + pci_dev = pci224_find_pci_dev(dev, it); + if (!pci_dev) + return -EIO; + + return pci224_attach_common(dev, pci_dev, it->options); +} + +static int +pci224_auto_attach(struct comedi_device *dev, unsigned long context_unused) +{ + struct pci_dev *pci_dev = comedi_to_pci_dev(dev); + struct pci224_private *devpriv; + + dev_info(dev->class_dev, DRIVER_NAME ": attach pci %s\n", + pci_name(pci_dev)); + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + dev->board_ptr = pci224_find_pci_board(pci_dev); + if (dev->board_ptr == NULL) { + dev_err(dev->class_dev, + DRIVER_NAME ": BUG! cannot determine board type!\n"); + return -EINVAL; + } + /* + * Need to 'get' the PCI device to match the 'put' in pci224_detach(). + * TODO: Remove the pci_dev_get() and matching pci_dev_put() once + * support for manual attachment of PCI devices via pci224_attach() + * has been removed. + */ + pci_dev_get(pci_dev); + return pci224_attach_common(dev, pci_dev, NULL); +} + +static void pci224_detach(struct comedi_device *dev) +{ + struct pci224_private *devpriv = dev->private; + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->subdevices) { + struct comedi_subdevice *s; + + s = &dev->subdevices[0]; + /* AO subdevice */ + kfree(s->range_table_list); + } + if (devpriv) { + kfree(devpriv->ao_readback); + kfree(devpriv->ao_scan_vals); + kfree(devpriv->ao_scan_order); + } + comedi_pci_disable(dev); + if (pcidev) + pci_dev_put(pcidev); +} + +static struct comedi_driver amplc_pci224_driver = { + .driver_name = "amplc_pci224", + .module = THIS_MODULE, + .attach = pci224_attach, + .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, &lc_pci224_driver, + id->driver_data); +} + +static const struct pci_device_id amplc_pci224_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI224) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI234) }, + { 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 low-level driver"); +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 00000000000..3895bc7cb3e --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pci230.c @@ -0,0 +1,2825 @@ + /* + 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: Wed, 22 Oct 2008 12:34:49 +0100 +Devices: [Amplicon] PCI230 (pci230 or amplc_pci230), + PCI230+ (pci230+ or amplc_pci230), + PCI260 (pci260 or amplc_pci230), PCI260+ (pci260+ or amplc_pci230) +Status: works + +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. + +Configuring a "amplc_pci230" will match any supported card and it will +choose the best match, picking the "+" models if possible. Configuring +a "pci230" will match a PCI230 or PCI230+ card and it will be treated as +a PCI230. Configuring a "pci260" will match a PCI260 or PCI260+ card +and it will be treated as a PCI260. Configuring a "pci230+" will match +a PCI230+ card. Configuring a "pci260+" will match a PCI260+ card. + +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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "8253.h" +#include "8255.h" + +/* PCI230 PCI configuration register information */ +#define PCI_DEVICE_ID_PCI230 0x0000 +#define PCI_DEVICE_ID_PCI260 0x0006 +#define PCI_DEVICE_ID_INVALID 0xffff + +#define PCI230_IO1_SIZE 32 /* Size of I/O space 1 */ +#define PCI230_IO2_SIZE 16 /* Size of I/O space 2 */ + +/* 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_Z2_CT0 0x14 /* 82C54 counter/timer 0 */ +#define PCI230_Z2_CT1 0x15 /* 82C54 counter/timer 1 */ +#define PCI230_Z2_CT2 0x16 /* 82C54 counter/timer 2 */ +#define PCI230_Z2_CTC 0x17 /* 82C54 counter/timer control word */ +#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) */ + +/* Convertor related constants. */ +#define PCI230_DAC_SETTLE 5 /* Analogue output settling time in µs */ + /* (DAC itself is 1µs nominally). */ +#define PCI230_ADC_SETTLE 1 /* Analogue input settling time in µs */ + /* (ADC itself is 1.6µs nominally but we poll + * anyway). */ +#define PCI230_MUX_SETTLE 10 /* ADC MUX settling time in µS */ + /* - 10µs for se, 20µs de. */ + +/* 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) /* Indicates 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 */ + +/* Value to write to ADCSWTRIG to trigger ADC conversion in software trigger + * mode. Can be anything. */ +#define PCI230_ADC_CONV 0xffff + +/* PCI230+ EXTFUNC values. */ +#define PCI230P_EXTFUNC_GAT_EXTTRIG (1<<0) + /* Route EXTTRIG pin to external gate inputs. */ +/* PCI230+ hardware version 2 values. */ +#define PCI230P2_EXTFUNC_DACFIFO (1<<1) + /* Allow DAC FIFO to be enabled. */ + +/* + * 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)) +/* Timebases in ns. */ +#define TIMEBASE_10MHZ 100 +#define TIMEBASE_1MHZ 1000 +#define TIMEBASE_100KHZ 10000 +#define TIMEBASE_10KHZ 100000 +#define TIMEBASE_1KHZ 1000000 + +/* + * 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) + +#define PCI230_TEST_BIT(val, n) ((val>>n)&1) + /* Assumes bits numbered with zero offset, ie. 0-15 */ + +/* (Potentially) shared resources and their owners */ +enum { + RES_Z2CT0, /* Z2-CT0 */ + RES_Z2CT1, /* Z2-CT1 */ + RES_Z2CT2, /* Z2-CT2 */ + NUM_RESOURCES /* Number of (potentially) shared resources. */ +}; + +enum { + OWNER_NONE, /* Not owned */ + OWNER_AICMD, /* Owned by AI command */ + OWNER_AOCMD /* Owned by AO command */ +}; + +/* + * 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 flags for atomic bit operations */ +#define AI_CMD_STARTED 0 +#define AO_CMD_STARTED 1 + +/* + * Board descriptions for the two boards supported. + */ + +struct pci230_board { + const char *name; + unsigned short id; + int ai_chans; + int ai_bits; + int ao_chans; + int ao_bits; + int have_dio; + unsigned int min_hwver; /* Minimum hardware version supported. */ +}; +static const struct pci230_board pci230_boards[] = { + { + .name = "pci230+", + .id = PCI_DEVICE_ID_PCI230, + .ai_chans = 16, + .ai_bits = 16, + .ao_chans = 2, + .ao_bits = 12, + .have_dio = 1, + .min_hwver = 1, + }, + { + .name = "pci260+", + .id = PCI_DEVICE_ID_PCI260, + .ai_chans = 16, + .ai_bits = 16, + .ao_chans = 0, + .ao_bits = 0, + .have_dio = 0, + .min_hwver = 1, + }, + { + .name = "pci230", + .id = PCI_DEVICE_ID_PCI230, + .ai_chans = 16, + .ai_bits = 12, + .ao_chans = 2, + .ao_bits = 12, + .have_dio = 1, + }, + { + .name = "pci260", + .id = PCI_DEVICE_ID_PCI260, + .ai_chans = 16, + .ai_bits = 12, + .ao_chans = 0, + .ao_bits = 0, + .have_dio = 0, + }, + { + .name = "amplc_pci230", /* Wildcard matches any above */ + .id = PCI_DEVICE_ID_INVALID, + }, +}; + +/* 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 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 state; /* State flags */ + unsigned long iobase1; /* PCI230's I/O space 1 */ + unsigned int ao_readback[2]; /* Used for AO readback */ + unsigned int ai_scan_count; /* Number of analogue input scans + * remaining. */ + unsigned int ai_scan_pos; /* Current position within analogue + * input scan */ + unsigned int ao_scan_count; /* Number of analogue output scans + * remaining. */ + int intr_cpuid; /* ID of CPU running interrupt routine. */ + unsigned short hwver; /* Hardware version (for '+' models). */ + unsigned short adccon; /* ADCCON register value. */ + unsigned short daccon; /* DACCON register value. */ + unsigned short adcfifothresh; /* ADC FIFO programmable interrupt + * level threshold (PCI230+/260+). */ + unsigned short adcg; /* ADCG register value. */ + unsigned char int_en; /* Interrupt enables bits. */ + unsigned char ai_bipolar; /* Set if bipolar input range so we + * know to mangle it. */ + unsigned char ao_bipolar; /* Set if bipolar output range so we + * know to mangle it. */ + unsigned char ier; /* Copy of interrupt enables/status register. */ + unsigned char intr_running; /* Flag set in interrupt routine. */ + unsigned char res_owner[NUM_RESOURCES]; /* Shared resource owners. */ +}; + +/* PCI230 clock source periods in ns */ +static const unsigned int pci230_timebase[8] = { + [CLK_10MHZ] = TIMEBASE_10MHZ, + [CLK_1MHZ] = TIMEBASE_1MHZ, + [CLK_100KHZ] = TIMEBASE_100KHZ, + [CLK_10KHZ] = TIMEBASE_10KHZ, + [CLK_1KHZ] = TIMEBASE_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 adccon bipolar flag for each analogue input range. */ +static const unsigned char pci230_ai_bipolar[7] = { 1, 1, 1, 1, 0, 0, 0 }; + +/* PCI230 analogue output range table */ +static const struct comedi_lrange pci230_ao_range = { + 2, { + UNI_RANGE(10), + BIP_RANGE(10) + } +}; + +/* PCI230 daccon bipolar flag for each analogue output range. */ +static const unsigned char pci230_ao_bipolar[2] = { 0, 1 }; + +static unsigned short pci230_ai_read(struct comedi_device *dev) +{ + const struct pci230_board *thisboard = comedi_board(dev); + struct pci230_private *devpriv = dev->private; + unsigned short data; + + /* Read sample. */ + data = inw(dev->iobase + 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. */ + data = data >> (16 - thisboard->ai_bits); + + /* If a bipolar range was specified, mangle it (twos + * complement->straight binary). */ + if (devpriv->ai_bipolar) + data ^= 1 << (thisboard->ai_bits - 1); + + return data; +} + +static inline unsigned short pci230_ao_mangle_datum(struct comedi_device *dev, + unsigned short datum) +{ + const struct pci230_board *thisboard = comedi_board(dev); + struct pci230_private *devpriv = dev->private; + + /* If a bipolar range was specified, mangle it (straight binary->twos + * complement). */ + if (devpriv->ao_bipolar) + datum ^= 1 << (thisboard->ao_bits - 1); + + /* 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); + return datum; +} + +static inline void pci230_ao_write_nofifo(struct comedi_device *dev, + unsigned short datum, + unsigned int chan) +{ + struct pci230_private *devpriv = dev->private; + + /* Store unmangled datum to be read back later. */ + devpriv->ao_readback[chan] = datum; + + /* Write mangled datum to appropriate DACOUT register. */ + outw(pci230_ao_mangle_datum(dev, datum), dev->iobase + (((chan) == 0) + ? PCI230_DACOUT1 + : + PCI230_DACOUT2)); +} + +static inline void pci230_ao_write_fifo(struct comedi_device *dev, + unsigned short datum, unsigned int chan) +{ + struct pci230_private *devpriv = dev->private; + + /* Store unmangled datum to be read back later. */ + devpriv->ao_readback[chan] = datum; + + /* Write mangled datum to appropriate DACDATA register. */ + outw(pci230_ao_mangle_datum(dev, datum), + dev->iobase + PCI230P2_DACDATA); +} + +static int get_resources(struct comedi_device *dev, unsigned int res_mask, + unsigned char owner) +{ + struct pci230_private *devpriv = dev->private; + int ok; + unsigned int i; + unsigned int b; + unsigned int claimed; + unsigned long irqflags; + + ok = 1; + claimed = 0; + spin_lock_irqsave(&devpriv->res_spinlock, irqflags); + for (b = 1, i = 0; (i < NUM_RESOURCES) + && (res_mask != 0); b <<= 1, i++) { + if ((res_mask & b) != 0) { + res_mask &= ~b; + if (devpriv->res_owner[i] == OWNER_NONE) { + devpriv->res_owner[i] = owner; + claimed |= b; + } else if (devpriv->res_owner[i] != owner) { + for (b = 1, i = 0; claimed != 0; b <<= 1, i++) { + if ((claimed & b) != 0) { + devpriv->res_owner[i] + = OWNER_NONE; + claimed &= ~b; + } + } + ok = 0; + break; + } + } + } + spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); + return ok; +} + +static inline int get_one_resource(struct comedi_device *dev, + unsigned int resource, unsigned char owner) +{ + return get_resources(dev, (1U << resource), owner); +} + +static void put_resources(struct comedi_device *dev, unsigned int res_mask, + unsigned char owner) +{ + struct pci230_private *devpriv = dev->private; + unsigned int i; + unsigned int b; + unsigned long irqflags; + + spin_lock_irqsave(&devpriv->res_spinlock, irqflags); + for (b = 1, i = 0; (i < NUM_RESOURCES) + && (res_mask != 0); b <<= 1, i++) { + if ((res_mask & b) != 0) { + res_mask &= ~b; + if (devpriv->res_owner[i] == owner) + devpriv->res_owner[i] = OWNER_NONE; + + } + } + spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); +} + +static inline void put_one_resource(struct comedi_device *dev, + unsigned int resource, unsigned char owner) +{ + put_resources(dev, (1U << resource), owner); +} + +static inline void put_all_resources(struct comedi_device *dev, + unsigned char owner) +{ + put_resources(dev, (1U << NUM_RESOURCES) - 1, owner); +} + +static unsigned int divide_ns(uint64_t ns, unsigned int timebase, + unsigned int round_mode) +{ + uint64_t div; + unsigned int rem; + + div = ns; + rem = do_div(div, timebase); + round_mode &= TRIG_ROUND_MASK; + switch (round_mode) { + default: + case TRIG_ROUND_NEAREST: + div += (rem + (timebase / 2)) / timebase; + break; + case TRIG_ROUND_DOWN: + break; + case TRIG_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 round_mode) +{ + unsigned int clk_src, cnt; + + for (clk_src = CLK_10MHZ;; clk_src++) { + cnt = divide_ns(ns, pci230_timebase[clk_src], round_mode); + 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 round) +{ + unsigned int count; + unsigned int clk_src; + + clk_src = pci230_choose_clk_count(*ns, &count, round); + *ns = count * pci230_timebase[clk_src]; + return; +} + +static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct, + unsigned int mode, uint64_t ns, + unsigned int round) +{ + struct pci230_private *devpriv = dev->private; + unsigned int clk_src; + unsigned int count; + + /* Set mode. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, mode); + /* Determine clock source and count. */ + clk_src = pci230_choose_clk_count(ns, &count, round); + /* Program clock source. */ + outb(CLK_CONFIG(ct, clk_src), devpriv->iobase1 + PCI230_ZCLK_SCE); + /* Set initial count. */ + if (count >= 65536) + count = 0; + + i8254_write(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, count); +} + +static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct) +{ + struct pci230_private *devpriv = dev->private; + + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, + I8254_MODE1); + /* Counter ct, 8254 mode 1, initial count not written. */ +} + +static int pci230_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + PCI230_ADCCON); + if ((status & PCI230_ADC_FIFO_EMPTY) == 0) + return 0; + return -EBUSY; +} + +static int pci230_ai_rinsn(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. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, I8254_MODE0); + devpriv->ai_bipolar = pci230_ai_bipolar[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, dev->iobase + PCI230_ADCEN); + + /* Set gain for channel. */ + outw(devpriv->adcg, dev->iobase + PCI230_ADCG); + + /* Specify uni/bip, se/diff, conversion source, and reset FIFO. */ + devpriv->adccon = adccon; + outw(adccon | PCI230_ADC_FIFO_RESET, dev->iobase + PCI230_ADCCON); + + /* Convert n samples */ + for (n = 0; n < insn->n; n++) { + /* Trigger conversion by toggling Z2-CT2 output (finish with + * output high). */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, + I8254_MODE0); + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 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; +} + +/* + * COMEDI_SUBD_AO instructions; + */ +static int pci230_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + struct pci230_private *devpriv = dev->private; + int i; + int chan, range; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* Set range - see analogue output range table; 0 => unipolar 10V, + * 1 => bipolar +/-10V range scale */ + devpriv->ao_bipolar = pci230_ao_bipolar[range]; + outw(range, dev->iobase + PCI230_DACCON); + + /* 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++) { + /* Write value to DAC and store it. */ + pci230_ao_write_nofifo(dev, data[i], chan); + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int pci230_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + struct pci230_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 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 = comedi_board(dev); + struct pci230_private *devpriv = dev->private; + int err = 0; + unsigned int tmp; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_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 |= cfc_check_trigger_src(&cmd->scan_begin_src, tmp); + + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SPEED_AO 8000 /* 8000 ns => 125 kHz */ +#define MIN_SPEED_AO 4294967295u /* 4294967295ns = 4.29s */ + /*- Comedi limit due to unsigned int cmd. Driver limit + * = 2^16 (16bit * counter) * 1000000ns (1kHz onboard + * clock) = 65.536s */ + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + MAX_SPEED_AO); + err |= cfc_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) != 0) { + 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))) != 0) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & + ~(CR_EDGE | CR_INVERT)); + err |= -EINVAL; + } + break; + default: + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + break; + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_NONE) + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments. + * "argument conflict" returned by comedilib to user mode process + * if this fails. */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + pci230_ns_to_single_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + 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; + int started; + struct comedi_cmd *cmd; + + spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); + started = test_and_clear_bit(AO_CMD_STARTED, &devpriv->state); + 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->int_en &= ~intsrc; + while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + } + if (devpriv->ier != devpriv->int_en) { + devpriv->ier = devpriv->int_en; + outb(devpriv->ier, devpriv->iobase1 + 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, + dev->iobase + PCI230_DACCON); + } + /* Release resources. */ + put_all_resources(dev, OWNER_AOCMD); +} + +static void pci230_handle_ao_nofifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + unsigned short data; + int i, ret; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + if (cmd->stop_src == TRIG_COUNT && devpriv->ao_scan_count == 0) + return; + for (i = 0; i < cmd->chanlist_len; i++) { + /* Read sample from Comedi's circular buffer. */ + ret = comedi_buf_get(s, &data); + if (ret == 0) { + s->async->events |= COMEDI_CB_OVERFLOW; + pci230_ao_stop(dev, s); + comedi_error(dev, "AO buffer underrun"); + return; + } + /* Write value to DAC. */ + pci230_ao_write_nofifo(dev, data, CR_CHAN(cmd->chanlist[i])); + } + async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + if (cmd->stop_src == TRIG_COUNT) { + devpriv->ao_scan_count--; + if (devpriv->ao_scan_count == 0) { + /* End of acquisition. */ + async->events |= COMEDI_CB_EOA; + pci230_ao_stop(dev, s); + } + } +} + +/* Loads DAC FIFO (if using it) from buffer. */ +/* Returns 0 if AO finished due to completion or error, 1 if still going. */ +static int 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; + unsigned int room; + unsigned short dacstat; + unsigned int i, n; + unsigned int events = 0; + int running; + + /* Get DAC FIFO status. */ + dacstat = inw(dev->iobase + PCI230_DACCON); + /* Determine number of scans available in buffer. */ + num_scans = comedi_buf_read_n_available(s) / cfc_bytes_per_scan(s); + if (cmd->stop_src == TRIG_COUNT) { + /* Fixed number of scans. */ + if (num_scans > devpriv->ao_scan_count) + num_scans = devpriv->ao_scan_count; + if (devpriv->ao_scan_count == 0) { + /* End of acquisition. */ + events |= COMEDI_CB_EOA; + } + } + if (events == 0) { + /* Check for FIFO underrun. */ + if ((dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) != 0) { + comedi_error(dev, "AO FIFO underrun"); + 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)) { + comedi_error(dev, "AO buffer underrun"); + 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) != 0) + room = PCI230P2_DAC_FIFOROOM_FULL; + else if ((dacstat & PCI230P2_DAC_FIFO_HALF) != 0) + room = PCI230P2_DAC_FIFOROOM_HALFTOFULL; + else if ((dacstat & PCI230P2_DAC_FIFO_EMPTY) != 0) + 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 short datum; + + comedi_buf_get(s, &datum); + pci230_ao_write_fifo(dev, datum, + CR_CHAN(cmd->chanlist[i])); + } + } + events |= COMEDI_CB_EOS | COMEDI_CB_BLOCK; + if (cmd->stop_src == TRIG_COUNT) { + devpriv->ao_scan_count -= num_scans; + if (devpriv->ao_scan_count == 0) { + /* All data for the command has been written + * to FIFO. Set FIFO interrupt trigger level + * to 'empty'. */ + devpriv->daccon = (devpriv->daccon + & + ~PCI230P2_DAC_INT_FIFO_MASK) + | PCI230P2_DAC_INT_FIFO_EMPTY; + outw(devpriv->daccon, + dev->iobase + PCI230_DACCON); + } + } + /* Check if FIFO underrun occurred while writing to FIFO. */ + dacstat = inw(dev->iobase + PCI230_DACCON); + if ((dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) != 0) { + comedi_error(dev, "AO FIFO underrun"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } + } + if ((events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) + != 0) { + /* Stopping AO due to completion or error. */ + pci230_ao_stop(dev, s); + running = 0; + } else { + running = 1; + } + async->events |= events; + return running; +} + +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 != 0) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); + if (test_bit(AO_CMD_STARTED, &devpriv->state)) { + /* 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_event(dev, s); + } else { + /* Using DAC FIFO. */ + /* Read DACSWTRIG register to trigger conversion. */ + inw(dev->iobase + 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); + } else { + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags); + } + + 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; + + set_bit(AO_CMD_STARTED, &devpriv->state); + if (cmd->stop_src == TRIG_COUNT && devpriv->ao_scan_count == 0) { + /* An empty acquisition! */ + async->events |= COMEDI_CB_EOA; + pci230_ao_stop(dev, s); + comedi_event(dev, s); + } else { + if (devpriv->hwver >= 2) { + /* Using DAC FIFO. */ + unsigned short scantrig; + int run; + + /* Preload FIFO data. */ + run = pci230_handle_ao_fifo(dev, s); + comedi_event(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, dev->iobase + 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->int_en |= PCI230_INT_ZCLK_CT1; + devpriv->ier |= PCI230_INT_ZCLK_CT1; + outb(devpriv->ier, + devpriv->iobase1 + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, + irqflags); + } + /* Set CT1 gate high to start counting. */ + outb(GAT_CONFIG(1, GAT_VCC), + devpriv->iobase1 + 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->int_en |= PCI230P2_INT_DAC; + devpriv->ier |= PCI230P2_INT_DAC; + outb(devpriv->ier, devpriv->iobase1 + 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 (!get_one_resource(dev, RES_Z2CT1, OWNER_AOCMD)) + return -EBUSY; + + } + + /* Get number of scans required. */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->ao_scan_count = cmd->stop_arg; + else /* TRIG_NONE, user calls cancel */ + devpriv->ao_scan_count = 0; + + /* Set range - see analogue output range table; 0 => unipolar 10V, + * 1 => bipolar +/-10V range scale */ + range = CR_RANGE(cmd->chanlist[0]); + devpriv->ao_bipolar = pci230_ao_bipolar[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, dev->iobase + 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, dev->iobase + 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), + devpriv->iobase1 + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, + cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + } + + /* 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; + unsigned int prev_polarity = 0; + 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); + unsigned int polarity = pci230_ai_bipolar[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 (polarity != prev_polarity) { + 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_polarity = polarity; + } + + if (subseq_len == 0) + subseq_len = cmd->chanlist_len; + + if ((cmd->chanlist_len % subseq_len) != 0) { + 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]) != 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 = comedi_board(dev); + struct pci230_private *devpriv = dev->private; + int err = 0; + unsigned int tmp; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_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 |= cfc_check_trigger_src(&cmd->scan_begin_src, tmp); + err |= cfc_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_INT | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_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 */ +#define MIN_SPEED_AI 4294967295u /* 4294967295ns = 4.29s */ + /*- Comedi limit due to unsigned int cmd. Driver limit + * = 2^16 (16bit * counter) * 1000000ns (1kHz onboard + * clock) = 65.536s */ + + 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 |= cfc_check_trigger_arg_min(&cmd->convert_arg, + max_speed_ai); + err |= cfc_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) != 0) { + /* Trigger number must be 0. */ + if ((cmd->convert_arg & ~CR_FLAGS_MASK) != 0) { + 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 |= cfc_check_trigger_arg_max(&cmd->convert_arg, 1); + } + } else { + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_NONE) + err |= cfc_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) != 0) { + 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) != 0) { + 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 |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } + + if (err) + return 3; + + /* Step 4: fix up any arguments. + * "argument conflict" returned by comedilib to user mode process + * if this fails. */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + pci230_ns_to_single_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + 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 & TRIG_ROUND_MASK); + if (!pci230_ai_check_scan_period(cmd)) { + /* Was below minimum required. Round up. */ + pci230_ns_to_single_timer(&cmd->scan_begin_arg, + TRIG_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 scanlen = cmd->scan_end_arg; + unsigned int wake; + unsigned short triglev; + unsigned short adccon; + + if ((cmd->flags & TRIG_WAKE_EOS) != 0) { + /* Wake at end of scan. */ + wake = scanlen - devpriv->ai_scan_pos; + } else { + if (cmd->stop_src != TRIG_COUNT || + devpriv->ai_scan_count >= PCI230_ADC_FIFOLEVEL_HALFFULL || + scanlen >= PCI230_ADC_FIFOLEVEL_HALFFULL) { + wake = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else { + wake = (devpriv->ai_scan_count * scanlen) + - devpriv->ai_scan_pos; + } + } + 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, dev->iobase + 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, dev->iobase + 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; + + if (trig_num != 0) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + if (test_bit(AI_CMD_STARTED, &devpriv->state)) { + unsigned int delayus; + + /* Trigger conversion by toggling Z2-CT2 output. Finish + * with output high. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, + I8254_MODE0); + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 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); + } else { + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + } + + 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 != 0) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + if (test_bit(AI_CMD_STARTED, &devpriv->state)) { + /* Trigger scan by waggling CT0 gate source. */ + zgat = GAT_CONFIG(0, GAT_GND); + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + zgat = GAT_CONFIG(0, GAT_VCC); + outb(zgat, devpriv->iobase1 + 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; + int started; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + started = test_and_clear_bit(AI_CMD_STARTED, &devpriv->state); + 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->int_en &= ~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); + } + if (devpriv->ier != devpriv->int_en) { + devpriv->ier = devpriv->int_en; + outb(devpriv->ier, devpriv->iobase1 + 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, + dev->iobase + PCI230_ADCCON); + /* Release resources. */ + put_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; + + set_bit(AI_CMD_STARTED, &devpriv->state); + if (cmd->stop_src == TRIG_COUNT && devpriv->ai_scan_count == 0) { + /* An empty acquisition! */ + async->events |= COMEDI_CB_EOA; + pci230_ai_stop(dev, s); + comedi_event(dev, s); + } else { + /* Enable ADC FIFO trigger level interrupt. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->int_en |= PCI230_INT_ADC; + devpriv->ier |= PCI230_INT_ADC; + outb(devpriv->ier, devpriv->iobase1 + 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) != 0) { + 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 != 0) { + /* 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, dev->iobase + 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, devpriv->iobase1 + 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, devpriv->iobase1 + 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, devpriv->iobase1 + + 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. */ + put_one_resource(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 scanlen = cmd->scan_end_arg; + unsigned int events = 0; + unsigned int status_fifo; + unsigned int i; + unsigned int todo; + unsigned int fifoamount; + + /* Determine number of samples to read. */ + if (cmd->stop_src != TRIG_COUNT) { + todo = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else if (devpriv->ai_scan_count == 0) { + todo = 0; + } else if ((devpriv->ai_scan_count > PCI230_ADC_FIFOLEVEL_HALFFULL) + || (scanlen > PCI230_ADC_FIFOLEVEL_HALFFULL)) { + todo = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else { + todo = (devpriv->ai_scan_count * scanlen) + - devpriv->ai_scan_pos; + if (todo > PCI230_ADC_FIFOLEVEL_HALFFULL) + todo = 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(dev->iobase + PCI230_ADCCON); + if ((status_fifo & PCI230_ADC_FIFO_FULL_LATCHED) != 0) { + /* Report error otherwise FIFO overruns will go + * unnoticed by the caller. */ + comedi_error(dev, "AI FIFO overrun"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + break; + } else if ((status_fifo & PCI230_ADC_FIFO_EMPTY) != 0) { + /* FIFO empty. */ + break; + } else if ((status_fifo & PCI230_ADC_FIFO_HALF) != 0) { + /* FIFO half full. */ + fifoamount = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else { + /* FIFO not empty. */ + if (devpriv->hwver > 0) { + /* Read PCI230+/260+ ADC FIFO level. */ + fifoamount = inw(dev->iobase + + PCI230P_ADCFFLEV); + if (fifoamount == 0) { + /* Shouldn't happen. */ + break; + } + } else { + fifoamount = 1; + } + } + } + /* Read sample and store in Comedi's circular buffer. */ + if (comedi_buf_put(s, pci230_ai_read(dev)) == 0) { + events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; + comedi_error(dev, "AI buffer overflow"); + break; + } + fifoamount--; + devpriv->ai_scan_pos++; + if (devpriv->ai_scan_pos == scanlen) { + /* End of scan. */ + devpriv->ai_scan_pos = 0; + devpriv->ai_scan_count--; + async->events |= COMEDI_CB_EOS; + } + } + if (cmd->stop_src == TRIG_COUNT && devpriv->ai_scan_count == 0) { + /* End of acquisition. */ + events |= COMEDI_CB_EOA; + } else { + /* More samples required, tell Comedi to block. */ + events |= COMEDI_CB_BLOCK; + } + async->events |= events; + if ((async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW)) != 0) { + /* disable hardware conversions */ + pci230_ai_stop(dev, s); + } else { + /* update FIFO interrupt trigger level */ + 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 |= (1U << RES_Z2CT2); + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Using Z2-CT0 monostable to gate Z2-CT2 conversion timer */ + res_mask |= (1U << RES_Z2CT0); + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Using Z2-CT1 for scan frequency */ + res_mask |= (1U << RES_Z2CT1); + } + } + /* Claim resources. */ + if (!get_resources(dev, res_mask, OWNER_AICMD)) + return -EBUSY; + + + /* Get number of scans required. */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->ai_scan_count = cmd->stop_arg; + else /* TRIG_NONE, user calls cancel */ + devpriv->ai_scan_count = 0; + devpriv->ai_scan_pos = 0; /* Position within scan. */ + + /* 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 = pci230_ai_bipolar[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, dev->iobase + PCI230_ADCEN); + + /* Set channel gains. */ + outw(devpriv->adcg, dev->iobase + PCI230_ADCG); + + /* Set counter/timer 2 output high for use as the initial start + * conversion source. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 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, dev->iobase + 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. */ + udelay(25); + + /* Reset FIFO again. */ + outw(adccon | PCI230_ADC_FIFO_RESET, dev->iobase + 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, devpriv->iobase1 + 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 & TRIG_ROUND_MASK); + 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, devpriv->iobase1 + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 0, I8254_MODE1, + ((uint64_t) cmd->convert_arg + * cmd->scan_end_arg), + TRIG_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, devpriv->iobase1 + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, + cmd->scan_begin_arg, + cmd-> + flags & + TRIG_ROUND_MASK); + } + } + } + + 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; + struct comedi_device *dev = (struct comedi_device *)d; + struct pci230_private *devpriv = dev->private; + struct comedi_subdevice *s; + unsigned long irqflags; + + /* Read interrupt status/enable register. */ + status_int = inb(devpriv->iobase1 + PCI230_INT_STAT); + + if (status_int == PCI230_INT_DISABLE) + return IRQ_NONE; + + + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + valid_status_int = devpriv->int_en & status_int; + /* Disable triggered interrupts. + * (Only those interrupts that need re-enabling, are, later in the + * handler). */ + devpriv->ier = devpriv->int_en & ~status_int; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + devpriv->intr_running = 1; + 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) != 0) { + s = dev->write_subdev; + pci230_handle_ao_nofifo(dev, s); + comedi_event(dev, s); + } + + if ((valid_status_int & PCI230P2_INT_DAC) != 0) { + s = dev->write_subdev; + pci230_handle_ao_fifo(dev, s); + comedi_event(dev, s); + } + + if ((valid_status_int & PCI230_INT_ADC) != 0) { + s = dev->read_subdev; + pci230_handle_ai(dev, s); + comedi_event(dev, s); + } + + /* Reenable interrupts. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + if (devpriv->ier != devpriv->int_en) { + devpriv->ier = devpriv->int_en; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + } + devpriv->intr_running = 0; + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + 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; +} + +/* Look for PCI device matching requested board name, bus and slot. */ +static struct pci_dev *pci230_find_pci_dev(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct pci230_board *thisboard = comedi_board(dev); + struct pci_dev *pci_dev = NULL; + int bus = it->options[0]; + int slot = it->options[1]; + + for_each_pci_dev(pci_dev) { + /* Check vendor ID (same for all supported PCI boards). */ + if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON) + continue; + /* If bus/slot specified, check them. */ + if ((bus || slot) && + (bus != pci_dev->bus->number || + slot != PCI_SLOT(pci_dev->devfn))) + continue; + if (thisboard->id == PCI_DEVICE_ID_INVALID) { + /* Wildcard board matches any supported PCI board. */ + const struct pci230_board *foundboard; + + foundboard = pci230_find_pci_board(pci_dev); + if (foundboard == NULL) + continue; + /* Replace wildcard board_ptr. */ + dev->board_ptr = foundboard; + } else { + /* Need to match a specific board. */ + if (!pci230_match_pci_board(thisboard, pci_dev)) + continue; + } + return pci_dev; + } + dev_err(dev->class_dev, + "No supported board found! (req. bus %d, slot %d)\n", + bus, slot); + return NULL; +} + +static int pci230_alloc_private(struct comedi_device *dev) +{ + struct pci230_private *devpriv; + + 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); + return 0; +} + +/* Common part of attach and auto_attach. */ +static int pci230_attach_common(struct comedi_device *dev, + struct pci_dev *pci_dev) +{ + const struct pci230_board *thisboard = comedi_board(dev); + struct pci230_private *devpriv = dev->private; + struct comedi_subdevice *s; + unsigned long iobase1, iobase2; + /* PCI230's I/O spaces 1 and 2 respectively. */ + int rc; + + comedi_set_hw_dev(dev, &pci_dev->dev); + + 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. */ + iobase1 = pci_resource_start(pci_dev, 2); + iobase2 = 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, iobase1, iobase2); + devpriv->iobase1 = iobase1; + dev->iobase = iobase2; + /* Read bits of DACCON register - only the output range. */ + devpriv->daccon = inw(dev->iobase + 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(dev->iobase + 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_chans > 0) + && (devpriv->hwver >= 2)) { + /* Enable DAC FIFO functionality. */ + extfunc |= PCI230P2_EXTFUNC_DACFIFO; + } + } + outw(extfunc, dev->iobase + PCI230P_EXTFUNC); + if ((extfunc & PCI230P2_EXTFUNC_DACFIFO) != 0) { + /* Temporarily enable DAC FIFO, reset it and disable + * FIFO wraparound. */ + outw(devpriv->daccon | PCI230P2_DAC_FIFO_EN + | PCI230P2_DAC_FIFO_RESET, + dev->iobase + PCI230_DACCON); + /* Clear DAC FIFO channel enable register. */ + outw(0, dev->iobase + PCI230P2_DACEN); + /* Disable DAC FIFO. */ + outw(devpriv->daccon, dev->iobase + PCI230_DACCON); + } + } + /* Disable board's interrupts. */ + outb(0, devpriv->iobase1 + 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, dev->iobase + PCI230_ADCEN); + outw(devpriv->adcg, dev->iobase + PCI230_ADCG); + outw(devpriv->adccon | PCI230_ADC_FIFO_RESET, + dev->iobase + 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; + } + + 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 = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &pci230_ai_range; + s->insn_read = &pci230_ai_rinsn; + 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_chans > 0) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = thisboard->ao_chans; + s->maxdata = (1 << thisboard->ao_bits) - 1; + s->range_table = &pci230_ao_range; + s->insn_write = &pci230_ao_winsn; + s->insn_read = &pci230_ao_rinsn; + s->len_chanlist = thisboard->ao_chans; + 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; + } + } 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, + devpriv->iobase1 + PCI230_PPI_X_BASE); + if (rc) + return rc; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static int pci230_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pci230_board *thisboard = comedi_board(dev); + struct pci_dev *pci_dev; + int rc; + + dev_info(dev->class_dev, "amplc_pci230: attach %s %d,%d\n", + thisboard->name, it->options[0], it->options[1]); + + rc = pci230_alloc_private(dev); + if (rc) + return rc; + + pci_dev = pci230_find_pci_dev(dev, it); + if (!pci_dev) + return -EIO; + return pci230_attach_common(dev, pci_dev); +} + +static int pci230_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pci_dev = comedi_to_pci_dev(dev); + int rc; + + dev_info(dev->class_dev, "amplc_pci230: attach pci %s\n", + pci_name(pci_dev)); + + rc = pci230_alloc_private(dev); + if (rc) + return rc; + + dev->board_ptr = pci230_find_pci_board(pci_dev); + if (dev->board_ptr == NULL) { + dev_err(dev->class_dev, + "amplc_pci230: BUG! cannot determine board type!\n"); + return -EINVAL; + } + /* + * Need to 'get' the PCI device to match the 'put' in pci230_detach(). + * TODO: Remove the pci_dev_get() and matching pci_dev_put() once + * support for manual attachment of PCI devices via pci230_attach() + * has been removed. + */ + pci_dev_get(pci_dev); + return pci230_attach_common(dev, pci_dev); +} + +static void pci230_detach(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(dev); + if (pcidev) + pci_dev_put(pcidev); +} + +static struct comedi_driver amplc_pci230_driver = { + .driver_name = "amplc_pci230", + .module = THIS_MODULE, + .attach = pci230_attach, + .auto_attach = pci230_auto_attach, + .detach = pci230_detach, + .board_name = &pci230_boards[0].name, + .offset = sizeof(pci230_boards[0]), + .num_names = ARRAY_SIZE(pci230_boards), +}; + +static int amplc_pci230_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &lc_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 low-level driver"); +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 00000000000..93ed03ee416 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pci263.c @@ -0,0 +1,120 @@ +/* + 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 <linux/pci.h> + +#include "../comedidev.h" + +#define PCI263_DRIVER_NAME "amplc_pci263" + +/* PCI263 PCI configuration register information */ +#define PCI_DEVICE_ID_AMPLICON_PCI263 0x000c + +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_READABLE | 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 = PCI263_DRIVER_NAME, + .module = THIS_MODULE, + .auto_attach = pci263_auto_attach, + .detach = comedi_pci_disable, +}; + +static const struct pci_device_id pci263_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI263) }, + {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, &lc_pci263_driver, + id->driver_data); +} + +static struct pci_driver amplc_pci263_pci_driver = { + .name = PCI263_DRIVER_NAME, + .id_table = pci263_pci_table, + .probe = &lc_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 00000000000..e03dd6e7141 --- /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_WRITEABLE; + 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 00000000000..eb1b92d72e8 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_das16_cs.c @@ -0,0 +1,372 @@ +/* + 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 "../comedidev.h" + +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#include "comedi_fc.h" +#include "8253.h" + +#define DAS16CS_SIZE 18 + +#define DAS16CS_ADC_DATA 0 +#define DAS16CS_DIO_MUX 2 +#define DAS16CS_MISC1 4 +#define DAS16CS_MISC2 6 +#define DAS16CS_CTR0 8 +#define DAS16CS_CTR1 10 +#define DAS16CS_CTR2 12 +#define DAS16CS_CTR_CONTROL 14 +#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 int ao_readback[2]; + 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_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct das16cs_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned short status1; + unsigned short d; + int bit; + + for (i = 0; i < insn->n; i++) { + devpriv->ao_readback[chan] = data[i]; + d = 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 = (d >> 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); + } + + return i; +} + +static int das16cs_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct das16cs_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 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; + + 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_winsn; + s->insn_read = &das16cs_ao_rinsn; + } 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 00000000000..7377da1aff7 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcidas.c @@ -0,0 +1,1652 @@ +/* + 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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "8253.h" +#include "8255.h" +#include "amcc_s5933.h" +#include "comedi_fc.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 { + /* base addresses */ + unsigned long s5933_config; + unsigned long control_status; + unsigned long adc_fifo; + unsigned long pacer_counter_dio; + unsigned long ao_registers; + /* divisors of master clock for analog input pacing */ + unsigned int divisor1; + unsigned int divisor2; + /* number of analog input samples remaining */ + unsigned int count; + /* 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]; + /* divisors of master clock for analog output pacing */ + unsigned int ao_divisor1; + unsigned int ao_divisor2; + /* number of analog output samples remaining */ + unsigned int ao_count; + /* cached values for readback */ + unsigned short ao_value[2]; + unsigned int caldac_value[NUM_CHANNELS_8800]; + unsigned int trimpot_value[NUM_CHANNELS_8402]; + unsigned int dac08_value; + 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; + break; + } + 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 */ + devpriv->ao_value[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 */ + devpriv->ao_value[chan] = data[0]; + + /* send data */ + outw(data[0], devpriv->ao_registers + DACDATA); + + return insn->n; +} + +static int cb_pcidas_ao_readback_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcidas_private *devpriv = dev->private; + + data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)]; + + return 1; +} + +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 int caldac_8800_write(struct comedi_device *dev, unsigned int address, + uint8_t value) +{ + struct cb_pcidas_private *devpriv = dev->private; + static const int num_caldac_channels = 8; + static const int bitstream_length = 11; + unsigned int bitstream = ((address & 0x7) << 8) | value; + static const int caldac_8800_udelay = 1; + + if (address >= num_caldac_channels) { + comedi_error(dev, "illegal caldac channel"); + return -1; + } + + if (value == devpriv->caldac_value[address]) + return 1; + + devpriv->caldac_value[address] = value; + + 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); + + return 1; +} + +static int caldac_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const unsigned int channel = CR_CHAN(insn->chanspec); + + return caldac_8800_write(dev, channel, data[0]); +} + +static int caldac_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct cb_pcidas_private *devpriv = dev->private; + + data[0] = devpriv->caldac_value[CR_CHAN(insn->chanspec)]; + + return 1; +} + +/* 1602/16 pregain offset */ +static void dac08_write(struct comedi_device *dev, unsigned int value) +{ + struct cb_pcidas_private *devpriv = dev->private; + unsigned long cal_reg; + + if (devpriv->dac08_value != value) { + devpriv->dac08_value = value; + + cal_reg = devpriv->control_status + CALIBRATION_REG; + + value &= 0xff; + value |= cal_enable_bits(dev); + + /* latch the new value into the caldac */ + outw(value, cal_reg); + udelay(1); + outw(value | SELECT_DAC08_BIT, cal_reg); + udelay(1); + outw(value, cal_reg); + udelay(1); + } +} + +static int dac08_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + + for (i = 0; i < insn->n; i++) + dac08_write(dev, data[i]); + + return insn->n; +} + +static int dac08_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcidas_private *devpriv = dev->private; + + data[0] = devpriv->dac08_value; + + return 1; +} + +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 int cb_pcidas_trimpot_write(struct comedi_device *dev, + unsigned int channel, unsigned int value) +{ + const struct cb_pcidas_board *thisboard = comedi_board(dev); + struct cb_pcidas_private *devpriv = dev->private; + + if (devpriv->trimpot_value[channel] == value) + return 1; + + devpriv->trimpot_value[channel] = value; + switch (thisboard->trimpot) { + case AD7376: + trimpot_7376_write(dev, value); + break; + case AD8402: + trimpot_8402_write(dev, channel, value); + break; + default: + comedi_error(dev, "driver bug?"); + return -1; + break; + } + + return 1; +} + +static int trimpot_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int channel = CR_CHAN(insn->chanspec); + + return cb_pcidas_trimpot_write(dev, channel, data[0]); +} + +static int trimpot_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct cb_pcidas_private *devpriv = dev->private; + unsigned int channel = CR_CHAN(insn->chanspec); + + data[0] = devpriv->trimpot_value[channel]; + + return 1; +} + +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 = comedi_board(dev); + struct cb_pcidas_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + thisboard->ai_speed * cmd->chanlist_len); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + thisboard->ai_speed); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_NONE) + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_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 void cb_pcidas_ai_load_counters(struct comedi_device *dev) +{ + struct cb_pcidas_private *devpriv = dev->private; + unsigned long timer_base = devpriv->pacer_counter_dio + ADC8254; + + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + + i8254_write(timer_base, 0, 1, devpriv->divisor1); + i8254_write(timer_base, 0, 2, devpriv->divisor2); +} + +static int cb_pcidas_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct cb_pcidas_board *thisboard = comedi_board(dev); + 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) + cb_pcidas_ai_load_counters(dev); + + /* set number of conversions */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->count = cmd->chanlist_len * cmd->stop_arg; + /* enable interrupts */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->adc_fifo_bits |= INTE; + devpriv->adc_fifo_bits &= ~INT_MASK; + if (cmd->flags & TRIG_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 = comedi_board(dev); + struct cb_pcidas_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + thisboard->ao_scan_speed); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_NONE) + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, + &devpriv->ao_divisor1, + &devpriv->ao_divisor2, + &arg, cmd->flags); + err |= cfc_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 int cb_pcidas_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + const struct cb_pcidas_board *thisboard = comedi_board(dev); + struct cb_pcidas_private *devpriv = dev->private; + unsigned int num_bytes, num_points = thisboard->fifo_size; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + /* load up fifo */ + if (cmd->stop_src == TRIG_COUNT && devpriv->ao_count < num_points) + num_points = devpriv->ao_count; + + num_bytes = cfc_read_array_from_buffer(s, devpriv->ao_buffer, + num_points * sizeof(short)); + num_points = num_bytes / sizeof(short); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ao_count -= num_points; + /* write data to board's fifo */ + outsw(devpriv->ao_registers + DACDATA, devpriv->ao_buffer, num_bytes); + + /* 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 void cb_pcidas_ao_load_counters(struct comedi_device *dev) +{ + struct cb_pcidas_private *devpriv = dev->private; + unsigned long timer_base = devpriv->pacer_counter_dio + DAC8254; + + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + + i8254_write(timer_base, 0, 1, devpriv->ao_divisor1); + i8254_write(timer_base, 0, 2, devpriv->ao_divisor2); +} + +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) + cb_pcidas_ao_load_counters(dev); + + /* set number of conversions */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->ao_count = cmd->chanlist_len * cmd->stop_arg; + /* 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); + comedi_error(dev, "error setting dac pacer source"); + return -1; + break; + } + 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 = comedi_board(dev); + 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 int half_fifo = thisboard->fifo_size / 2; + unsigned int num_points; + 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_NONE || + (cmd->stop_src == TRIG_COUNT + && devpriv->ao_count)) { + comedi_error(dev, "dac fifo underflow"); + async->events |= COMEDI_CB_ERROR; + } + async->events |= COMEDI_CB_EOA; + } + } else if (status & DAHFI) { + unsigned int num_bytes; + + /* figure out how many points we are writing to fifo */ + num_points = half_fifo; + if (cmd->stop_src == TRIG_COUNT && + devpriv->ao_count < num_points) + num_points = devpriv->ao_count; + num_bytes = + cfc_read_array_from_buffer(s, devpriv->ao_buffer, + num_points * sizeof(short)); + num_points = num_bytes / sizeof(short); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ao_count -= num_points; + /* write data to board's fifo */ + outsw(devpriv->ao_registers + DACDATA, devpriv->ao_buffer, + num_points); + /* 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); + } + + cfc_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 = comedi_board(dev); + 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 = half_fifo; + if (cmd->stop_src == TRIG_COUNT && + num_samples > devpriv->count) { + num_samples = devpriv->count; + } + insw(devpriv->adc_fifo + ADCDATA, devpriv->ai_buffer, + num_samples); + cfc_write_array_to_buffer(s, devpriv->ai_buffer, + num_samples * sizeof(short)); + devpriv->count -= num_samples; + if (cmd->stop_src == TRIG_COUNT && devpriv->count == 0) + 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++) { + /* break if fifo is empty */ + if ((ADNE & inw(devpriv->control_status + + INT_ADCFIFO)) == 0) + break; + cfc_write_to_buffer(s, inw(devpriv->adc_fifo)); + if (cmd->stop_src == TRIG_COUNT && + --devpriv->count == 0) { + /* end of acquisition */ + 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) { + comedi_error(dev, + "bug! encountered end of acquisition interrupt?"); + /* 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) { + comedi_error(dev, "fifo overflow"); + /* 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_EOA | COMEDI_CB_ERROR; + } + + cfc_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); + devpriv->pacer_counter_dio = 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); + + if (request_irq(pcidev->irq, cb_pcidas_interrupt, + IRQF_SHARED, dev->driver->driver_name, dev)) { + dev_dbg(dev->class_dev, "unable to allocate irq %d\n", + pcidev->irq); + return -EINVAL; + } + dev->irq = pcidev->irq; + + 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; + s->insn_read = cb_pcidas_ao_readback_insn; + if (thisboard->has_ao_fifo) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + 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->insn_write = cb_pcidas_ao_nofifo_winsn; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8255 */ + s = &dev->subdevices[2]; + ret = subdev_8255_init(dev, s, NULL, + devpriv->pacer_counter_dio + 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_read = caldac_read_insn; + s->insn_write = caldac_write_insn; + for (i = 0; i < s->n_chan; i++) + caldac_8800_write(dev, 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_read = trimpot_read_insn; + s->insn_write = trimpot_write_insn; + for (i = 0; i < s->n_chan; i++) + cb_pcidas_trimpot_write(dev, 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->insn_read = dac08_read_insn; + s->insn_write = dac08_write_insn; + s->maxdata = 0xff; + dac08_write(dev, 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); + } + } + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(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 00000000000..035c3a17600 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcidas64.c @@ -0,0 +1,4119 @@ +/* + 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 +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "8253.h" +#include "8255.h" +#include "plx9080.h" +#include "comedi_fc.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, +}; + +/* devpriv->dio_counter_iobase 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) + } +}; + +/* 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) + } +}; + +/* 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) + } +}; + +/* 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) + } +}; + +/* 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; + 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; + else + 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, + .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, + .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_64xx, + .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_64xx, + .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_64xx, + .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, + .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, + .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, + .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, + .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, + .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, + .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_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_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_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, + .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, + .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, + .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, + .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, + .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, + .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_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_64xx, + .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_64xx, + .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_64xx, + .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_64xx, + .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_64xx, + .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_64xx, + .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 = comedi_board(dev); + + if ((thisboard->layout == LAYOUT_64XX && !use_differential) || + (thisboard->layout == LAYOUT_60XX && use_differential)) + return ADC_SE_DIFF_BIT; + else + 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; + void __iomem *dio_counter_iobase; + /* local address (used by dma controller) */ + uint32_t local0_iobase; + uint32_t local1_iobase; + /* number of analog input samples remaining */ + volatile unsigned int ai_count; + /* 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 */ + volatile 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 */ + volatile unsigned int ao_dma_index; + /* number of analog output samples remaining */ + volatile unsigned long ao_count; + /* remember what the analog outputs are set to, to allow readback */ + volatile unsigned int ao_value[2]; + unsigned int hw_revision; /* stc chip hardware revision number */ + /* last bits sent to INTR_ENABLE_REG register */ + volatile unsigned int intr_enable_bits; + /* last bits sent to ADC_CONTROL1_REG register */ + volatile uint16_t adc_control1_bits; + /* last bits sent to FIFO_SIZE_REG register */ + volatile uint16_t fifo_size_bits; + /* last bits sent to HW_CONFIG_REG register */ + volatile uint16_t hw_config_bits; + volatile uint16_t dac_control1_bits; + /* last bits written to plx9080 control register */ + volatile uint32_t plx_control_bits; + /* last bits written to plx interrupt control and status register */ + volatile uint32_t plx_intcsr_bits; + /* index of calibration source readable through ai ch0 */ + volatile int calibration_source; + /* bits written to i2c calibration/range register */ + volatile uint8_t i2c_cal_range_bits; + /* configure digital triggers to trigger on falling edge */ + volatile unsigned int ext_trig_falling; + /* states of various devices stored to enable read-back */ + unsigned int ad8402_state[2]; + unsigned int caldac_state[8]; + volatile 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 = comedi_board(dev); + const struct comedi_krange *range = + &thisboard->ai_range_table->range[range_index]; + unsigned int bits = 0; + + switch (range->max) { + case 10000000: + bits = 0x000; + break; + case 5000000: + bits = 0x100; + break; + case 2000000: + case 2500000: + bits = 0x200; + break; + case 1000000: + case 1250000: + bits = 0x300; + break; + case 500000: + bits = 0x400; + break; + case 200000: + case 250000: + bits = 0x500; + break; + case 100000: + bits = 0x600; + break; + case 50000: + bits = 0x700; + break; + default: + comedi_error(dev, "bug! in ai_range_bits_6xxx"); + break; + } + if (range->min == 0) + bits += 0x900; + return bits; +} + +static unsigned int hw_revision(const struct comedi_device *dev, + uint16_t hw_status_bits) +{ + const struct pcidas64_board *thisboard = comedi_board(dev); + + 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, + volatile uint16_t *bits, unsigned int channel, + unsigned int range) +{ + const struct pcidas64_board *thisboard = comedi_board(dev); + unsigned int code = thisboard->ao_range_code[range]; + + if (channel > 1) + comedi_error(dev, "bug! bad channel?"); + if (code & ~0x3) + comedi_error(dev, "bug! bad range code?"); + + *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 = comedi_board(dev); + 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 TRIG_WAKE_EOS flag is set. */ + if (cmd->flags & TRIG_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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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] == NULL) + 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] == NULL) + 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 == NULL) + 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 == NULL) + 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 inline void warn_external_queue(struct comedi_device *dev) +{ + comedi_error(dev, + "AO command and AI external channel queue cannot be used simultaneously."); + comedi_error(dev, + "Use internal AI channel queue (channels must be consecutive and use same range/aref)"); +} + +/* 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) { + comedi_error(dev, "i2c write failed: no acknowledge"); + 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) { + comedi_error(dev, "i2c write failed: no acknowledge"); + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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(devpriv->dio_counter_iobase + + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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; + break; + } + + 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 = comedi_board(dev); + + switch (thisboard->layout) { + case LAYOUT_4020: + return ai_config_master_clock_4020(dev, data); + break; + default: + return -EINVAL; + break; + } + + 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); + break; + case INSN_CONFIG_BLOCK_SIZE: + return ai_config_block_size(dev, data); + break; + case INSN_CONFIG_TIMER_1: + return ai_config_master_clock(dev, data); + break; + default: + return -EINVAL; + break; + } + 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 & TRIG_ROUND_MASK) { + case TRIG_ROUND_UP: + divisor = (ns + TIMER_BASE - 1) / TIMER_BASE; + break; + case TRIG_ROUND_DOWN: + divisor = ns / TIMER_BASE; + break; + case TRIG_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 = comedi_board(dev); + unsigned int convert_divisor = 0, 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) { + /* XXX check for integer overflows */ + 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; + } + + return; +} + +static int cb_pcidas64_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct pcidas64_board *board = comedi_board(dev); + 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 = comedi_board(dev); + int err = 0; + unsigned int tmp_arg, tmp_arg2; + unsigned int triggers; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_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 |= cfc_check_trigger_src(&cmd->scan_begin_src, triggers); + + triggers = TRIG_TIMER; + if (thisboard->layout == LAYOUT_4020) + triggers |= TRIG_NOW; + else + triggers |= TRIG_EXT; + err |= cfc_check_trigger_src(&cmd->convert_src, triggers); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + } else { + err |= cfc_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 |= cfc_check_trigger_arg_min( + &cmd->scan_begin_arg, + cmd->convert_arg * + cmd->chanlist_len); + } + } + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_EXT: + break; + case TRIG_COUNT: + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_NONE: + err |= cfc_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; + else + return 0; +} + +static void setup_sample_counters(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + + if (cmd->stop_src == TRIG_COUNT) { + /* set software count */ + devpriv->ai_count = cmd->stop_arg * cmd->chanlist_len; + } + /* 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 = comedi_board(dev); + 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; + break; + } + 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 */ + comedi_error(dev, "bug! failed to set ai pacing!"); + 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 = comedi_board(dev); + + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 & TRIG_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 & TRIG_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; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + 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 (cmd->stop_src == TRIG_COUNT) { + if (devpriv->ai_count == 0) + break; + if (num_samples > devpriv->ai_count) + num_samples = devpriv->ai_count; + + devpriv->ai_count -= num_samples; + } + + if (num_samples < 0) { + dev_err(dev->class_dev, + "cb_pcidas64: bug! num_samples < 0\n"); + break; + } + + for (i = 0; i < num_samples; i++) { + cfc_write_to_buffer(s, + readw(devpriv->main_iobase + + ADC_FIFO_REG)); + } + + } 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; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int i; + unsigned int max_transfer = 100000; + 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; + + if (cmd->stop_src == TRIG_COUNT) { + if (max_transfer > devpriv->ai_count) + max_transfer = devpriv->ai_count; + + } + for (i = 0; read_code != write_code && i < max_transfer;) { + fifo_data = readl(devpriv->dio_counter_iobase + ADC_FIFO_REG); + cfc_write_to_buffer(s, fifo_data & 0xffff); + i++; + if (i < max_transfer) { + cfc_write_to_buffer(s, (fifo_data >> 16) & 0xffff); + i++; + } + read_code = readw(devpriv->main_iobase + ADC_READ_PNTR_REG) & + 0x7fff; + } + devpriv->ai_count -= i; +} + +/* empty fifo */ +static void pio_drain_ai_fifo(struct comedi_device *dev) +{ + const struct pcidas64_board *thisboard = comedi_board(dev); + + 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 = comedi_board(dev); + struct pcidas64_private *devpriv = dev->private; + struct comedi_async *async = dev->read_subdev->async; + struct comedi_cmd *cmd = &async->cmd; + 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 = dma_transfer_size(dev); + if (cmd->stop_src == TRIG_COUNT) { + if (num_samples > devpriv->ai_count) + num_samples = devpriv->ai_count; + devpriv->ai_count -= num_samples; + } + cfc_write_array_to_buffer(dev->read_subdev, + devpriv->ai_buffer[devpriv-> + ai_dma_index], + num_samples * sizeof(uint16_t)); + 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 = comedi_board(dev); + 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) { + comedi_error(dev, "fifo overrun"); + async->events |= COMEDI_CB_EOA | 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 & TRIG_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 && (int)devpriv->ai_count <= 0) || + (cmd->stop_src == TRIG_EXT && (status & ADC_STOP_BIT))) { + async->events |= COMEDI_CB_EOA; + } + + cfc_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 int ao_stopped_by_error(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + + if (cmd->stop_src == TRIG_NONE) + return 1; + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->ao_count) + return 1; + if (last_ao_dma_load_completed(dev) == 0) + return 1; + } + return 0; +} + +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 load_ao_dma_buffer(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int num_bytes, buffer_index, prev_buffer_index; + unsigned int next_bits; + + buffer_index = devpriv->ao_dma_index; + prev_buffer_index = prev_ao_dma_index(dev); + + num_bytes = comedi_buf_read_n_available(dev->write_subdev); + if (num_bytes > DMA_BUFFER_SIZE) + num_bytes = DMA_BUFFER_SIZE; + if (cmd->stop_src == TRIG_COUNT && num_bytes > devpriv->ao_count) + num_bytes = devpriv->ao_count; + num_bytes -= num_bytes % bytes_in_sample; + + if (num_bytes == 0) + return 0; + + num_bytes = cfc_read_array_from_buffer(dev->write_subdev, + devpriv-> + ao_buffer[buffer_index], + num_bytes); + devpriv->ao_dma_desc[buffer_index].transfer_size = + cpu_to_le32(num_bytes); + /* 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; + devpriv->ao_count -= num_bytes; + + return num_bytes; +} + +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 == NULL) + 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)) { + async->events |= COMEDI_CB_EOA; + if (ao_stopped_by_error(dev, cmd)) + async->events |= COMEDI_CB_ERROR; + } + cfc_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 = comedi_board(dev); + 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 */ + devpriv->ao_value[chan] = data[0]; + + return 1; +} + +static int ao_readback_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pcidas64_private *devpriv = dev->private; + + data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)]; + + 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) + comedi_error(dev, "bug! last ao channel < first ao channel"); + + 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) { + comedi_error(dev, "bug! ao divisor too big"); + 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; + unsigned int num_bytes; + 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); + + num_bytes = (DAC_FIFO_SIZE / 2) * bytes_in_sample; + if (cmd->stop_src == TRIG_COUNT && + num_bytes / bytes_in_sample > devpriv->ao_count) + num_bytes = devpriv->ao_count * bytes_in_sample; + num_bytes = cfc_read_array_from_buffer(dev->write_subdev, + devpriv->ao_bounce_buffer, + num_bytes); + for (i = 0; i < num_bytes / bytes_in_sample; i++) { + writew(devpriv->ao_bounce_buffer[i], + devpriv->main_iobase + DAC_FIFO_REG); + } + devpriv->ao_count -= num_bytes / bytes_in_sample; + if (cmd->stop_src == TRIG_COUNT && devpriv->ao_count == 0) + return 0; + num_bytes = load_ao_dma_buffer(dev, cmd); + if (num_bytes == 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 = comedi_board(dev); + + 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; + devpriv->ao_count = cmd->stop_arg * cmd->chanlist_len; + + 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 = comedi_board(dev); + int err = 0; + unsigned int tmp_arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_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 |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_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(int dir, int port, int data, unsigned long arg) +{ + void __iomem *iobase = (void __iomem *)arg; + if (dir) { + writeb(data, iobase + port); + return 0; + } else { + return readb(iobase + port); + } +} + +static int dio_callback_4020(int dir, int port, int data, unsigned long arg) +{ + void __iomem *iobase = (void __iomem *)arg; + if (dir) { + writew(data, iobase + 2 * port); + return 0; + } else { + return readw(iobase + 2 * port); + } +} + +static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int bits; + + bits = readb(devpriv->dio_counter_iobase + 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) +{ + struct pcidas64_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) + writeb(s->state, devpriv->dio_counter_iobase + 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) +{ + struct pcidas64_private *devpriv = dev->private; + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + writeb(s->io_bits, + devpriv->dio_counter_iobase + 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) +{ + struct pcidas64_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) { + writeb(s->state, + devpriv->dio_counter_iobase + DIO_DATA_60XX_REG); + } + + data[1] = readb(devpriv->dio_counter_iobase + 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) { + comedi_error(dev, "illegal caldac channel"); + 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: + comedi_error(dev, "invalid caldac channel\n"); + return -1; + break; + } + 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 = comedi_board(dev); + struct pcidas64_private *devpriv = dev->private; + + devpriv->caldac_state[channel] = value; + + 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 calib_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pcidas64_private *devpriv = dev->private; + int channel = CR_CHAN(insn->chanspec); + + /* return immediately if setting hasn't changed, since + * programming these things is slow */ + if (devpriv->caldac_state[channel] == data[0]) + return 1; + + caldac_write(dev, channel, data[0]); + + return 1; +} + +static int calib_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int channel = CR_CHAN(insn->chanspec); + + data[0] = devpriv->caldac_state[channel]; + + return 1; +} + +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; + + devpriv->ad8402_state[channel] = value; + + 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 ad8402_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pcidas64_private *devpriv = dev->private; + int channel = CR_CHAN(insn->chanspec); + + /* return immediately if setting hasn't changed, since + * programming these things is slow */ + if (devpriv->ad8402_state[channel] == data[0]) + return 1; + + devpriv->ad8402_state[channel] = data[0]; + + ad8402_write(dev, channel, data[0]); + + return 1; +} + +static int ad8402_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int channel = CR_CHAN(insn->chanspec); + + data[0] = devpriv->ad8402_state[channel]; + + return 1; +} + +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 = comedi_board(dev); + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s; + void __iomem *dio_8255_iobase; + 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_read = ao_readback_insn; + s->insn_write = ao_winsn; + 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 | SDF_READABLE; + 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) { + dio_8255_iobase = devpriv->main_iobase + I8255_4020_REG; + ret = subdev_8255_init(dev, s, dio_callback_4020, + (unsigned long)dio_8255_iobase); + } else { + dio_8255_iobase = + devpriv->dio_counter_iobase + DIO_8255_OFFSET; + ret = subdev_8255_init(dev, s, dio_callback, + (unsigned long)dio_8255_iobase); + } + 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_read = calib_read_insn; + s->insn_write = calib_write_insn; + for (i = 0; i < s->n_chan; i++) + caldac_write(dev, 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->insn_read = ad8402_read_insn; + s->insn_write = ad8402_write_insn; + s->maxdata = 0xff; + for (i = 0; i < s->n_chan; i++) + ad8402_write(dev, 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); + devpriv->dio_counter_iobase = pci_ioremap_bar(pcidev, 3); + + if (!devpriv->plx9080_iobase || !devpriv->main_iobase + || !devpriv->dio_counter_iobase) { + 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); + /* get irq */ + if (request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED, + "cb_pcidas64", dev)) { + dev_dbg(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); + + retval = setup_subdevices(dev); + if (retval < 0) + return retval; + + return 0; +} + +static void detach(struct comedi_device *dev) +{ + const struct pcidas64_board *thisboard = comedi_board(dev); + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct pcidas64_private *devpriv = dev->private; + unsigned int i; + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (pcidev) { + if (devpriv->plx9080_iobase) { + disable_plx_interrupts(dev); + iounmap(devpriv->plx9080_iobase); + } + if (devpriv->main_iobase) + iounmap(devpriv->main_iobase); + if (devpriv->dio_counter_iobase) + iounmap(devpriv->dio_counter_iobase); + /* 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); + } + } + comedi_pci_disable(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 00000000000..901dc5d1bb7 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcidda.c @@ -0,0 +1,429 @@ +/* + * 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] + * (Measurement Computing) PCI-DDA04/12 [pci-dda04/12] + * (Measurement Computing) PCI-DDA02/12 [pci-dda02/12] + * (Measurement Computing) PCI-DDA08/16 [pci-dda08/16] + * (Measurement Computing) PCI-DDA04/16 [pci-dda04/16] + * (Measurement Computing) 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 <linux/pci.h> + +#include "../comedidev.h" + +#include "comedi_fc.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 { + /* 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) +{ + 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(dev->iobase + 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, dev->iobase + DACALIBRATION1); + } +} + +/* reads a 16 bit value from board's eeprom */ +static unsigned int cb_pcidda_read_eeprom(struct comedi_device *dev, + unsigned int address) +{ + 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, dev->iobase + 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, dev->iobase + 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) +{ + 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, dev->iobase + DACALIBRATION2); + /* deactivate caldac */ + cal2_bits |= DESELECT_CALDAC_BIT(caldac); + outw_p(cal2_bits, dev->iobase + 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, dev->iobase + CB_DDA_DA_CTRL_REG); + + outw(data[0], dev->iobase + 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; + unsigned long iobase_8255; + 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, 3); + iobase_8255 = 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 = 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, iobase_8255 + (i * 4)); + 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_disable, +}; + +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 00000000000..50e522e6e69 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcimdas.c @@ -0,0 +1,302 @@ +/* + comedi/drivers/cb_pcimdas.c + Comedi driver for Computer Boards PCIM-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) +Author: Richard Bytheway +Updated: Wed, 13 Nov 2002 12:34:56 +0000 +Status: experimental + +Written to support the PCIM-DAS1602/16 on a 2.4 series kernel. + +Configuration Options: + [0] - PCI bus number + [1] - PCI slot number + +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. +See http://www.mccdaq.com/PDFs/Manuals/pcim-das1602-16.pdf for more details. +*/ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "plx9052.h" +#include "8255.h" + +/* Registers for the PCIM-DAS1602/16 */ + +/* sizes of io regions (bytes) */ +#define BADR3_SIZE 16 + +/* DAC Offsets */ +#define ADC_TRIG 0 +#define DAC0_OFFSET 2 +#define DAC1_OFFSET 4 + +/* AI and Counter Constants */ +#define MUX_LIMITS 0 +#define MAIN_CONN_DIO 1 +#define ADC_STAT 2 +#define ADC_CONV_STAT 3 +#define ADC_INT 4 +#define ADC_PACER 5 +#define BURST_MODE 6 +#define PROG_GAIN 7 +#define CLK8254_1_DATA 8 +#define CLK8254_2_DATA 9 +#define CLK8254_3_DATA 10 +#define CLK8254_CONTROL 11 +#define USER_COUNTER 12 +#define RESID_COUNT_H 13 +#define RESID_COUNT_L 14 + +/* + * 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 BADR3; + + /* Used for AO readback */ + unsigned int ao_readback[2]; +}; + +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 + 2); + if ((status & 0x80) == 0) + return 0; + return -EBUSY; +} + +static int cb_pcimdas_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct cb_pcimdas_private *devpriv = dev->private; + int n; + unsigned int d; + int chan = CR_CHAN(insn->chanspec); + unsigned short chanlims; + int maxchans; + int ret; + + /* only support sw initiated reads from a single channel */ + + /* check channel number */ + if ((inb(devpriv->BADR3 + 2) & 0x20) == 0) /* differential mode */ + maxchans = s->n_chan / 2; + else + maxchans = s->n_chan; + + if (chan > (maxchans - 1)) + return -ETIMEDOUT; /* *** Wrong error code. Fixme. */ + + /* configure for sw initiated read */ + d = inb(devpriv->BADR3 + 5); + if ((d & 0x03) > 0) { /* only reset if needed. */ + d = d & 0xfd; + outb(d, devpriv->BADR3 + 5); + } + + /* set bursting off, conversions on */ + outb(0x01, devpriv->BADR3 + 6); + + /* set range to 10V. UP/BP is controlled by a switch on the board */ + outb(0x00, devpriv->BADR3 + 7); + + /* + * write channel limits to multiplexer, set Low (bits 0-3) and + * High (bits 4-7) channels to chan. + */ + chanlims = chan | (chan << 4); + outb(chanlims, devpriv->BADR3 + 0); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outw(0, dev->iobase + 0); + + /* 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(dev->iobase + 0); + } + + /* return the number of samples read/written */ + return n; +} + +static int cb_pcimdas_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct cb_pcimdas_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + + /* 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++) { + switch (chan) { + case 0: + outw(data[i] & 0x0FFF, dev->iobase + DAC0_OFFSET); + break; + case 1: + outw(data[i] & 0x0FFF, dev->iobase + DAC1_OFFSET); + break; + default: + return -1; + } + devpriv->ao_readback[chan] = data[i]; + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int cb_pcimdas_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct cb_pcimdas_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 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; + unsigned long iobase_8255; + 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); + iobase_8255 = pci_resource_start(pcidev, 4); + + ret = comedi_alloc_subdevices(dev, 3); + 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; + s->n_chan = 16; + s->maxdata = 0xffff; + s->range_table = &range_unknown; + s->len_chanlist = 1; /* This is the maximum chanlist length that */ + /* the board can handle */ + s->insn_read = cb_pcimdas_ai_rinsn; + + s = &dev->subdevices[1]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0xfff; + /* ranges are hardware settable, but not software readable. */ + s->range_table = &range_unknown; + s->insn_write = &cb_pcimdas_ao_winsn; + s->insn_read = &cb_pcimdas_ao_rinsn; + + s = &dev->subdevices[2]; + /* digital i/o subdevice */ + ret = subdev_8255_init(dev, s, NULL, iobase_8255); + if (ret) + return ret; + + return 0; +} + +static void cb_pcimdas_detach(struct comedi_device *dev) +{ + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(dev); +} + +static struct comedi_driver cb_pcimdas_driver = { + .driver_name = "cb_pcimdas", + .module = THIS_MODULE, + .auto_attach = cb_pcimdas_auto_attach, + .detach = cb_pcimdas_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) }, + { 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 low-level driver"); +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 00000000000..4a2b200de01 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcimdda.c @@ -0,0 +1,225 @@ +/* + 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 <linux/pci.h> + +#include "../comedidev.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 + +#define MAX_AO_READBACK_CHANNELS 6 + +struct cb_pcimdda_private { + unsigned int ao_readback[MAX_AO_READBACK_CHANNELS]; +}; + +static int cb_pcimdda_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcimdda_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan); + unsigned int val = 0; + 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); + } + + /* Cache the last value for readback */ + devpriv->ao_readback[chan] = val; + + return insn->n; +} + +static int cb_pcimdda_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcimdda_private *devpriv = dev->private; + int chan = CR_CHAN(insn->chanspec); + unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan); + int i; + + for (i = 0; i < insn->n; i++) { + /* Initiate the simultaneous transfer */ + inw(offset); + + data[i] = devpriv->ao_readback[chan]; + } + + return insn->n; +} + +static int cb_pcimdda_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct cb_pcimdda_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, 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_winsn; + s->insn_read = cb_pcimdda_ao_rinsn; + + s = &dev->subdevices[1]; + /* digital i/o subdevice */ + ret = subdev_8255_init(dev, s, NULL, + dev->iobase + 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_disable, +}; + +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_bond.c b/drivers/staging/comedi/drivers/comedi_bond.c new file mode 100644 index 00000000000..8450c99af8b --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_bond.c @@ -0,0 +1,358 @@ +/* + * 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 { +# define MAX_BOARD_NAME 256 + char name[MAX_BOARD_NAME]; + 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]; + int left = + MAX_BOARD_NAME - strlen(devpriv->name) - 1; + snprintf(buf, sizeof(buf), "%u:%u ", + bdev->minor, bdev->subdev); + buf[sizeof(buf) - 1] = 0; + strncat(devpriv->name, buf, left); + } + + } + } + + 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_fc.c b/drivers/staging/comedi/drivers/comedi_fc.c new file mode 100644 index 00000000000..c33c3e5680a --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_fc.c @@ -0,0 +1,132 @@ +/* + * comedi_fc.c + * This is a place for code driver writers wish to share between + * two or more drivers. fc is short for frank-common. + * + * Author: Frank Mori Hess <fmhess@users.sourceforge.net> + * Copyright (C) 2002 Frank Mori Hess + * + * 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 "comedi_fc.h" + +unsigned int cfc_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 * bytes_per_sample(s); + num_samples = (cmd->chanlist_len + bits_per_sample - 1) / + bits_per_sample; + break; + default: + num_samples = cmd->chanlist_len; + break; + } + return num_samples * bytes_per_sample(s); +} +EXPORT_SYMBOL_GPL(cfc_bytes_per_scan); + +void cfc_inc_scan_progress(struct comedi_subdevice *s, unsigned int num_bytes) +{ + struct comedi_async *async = s->async; + unsigned int scan_length = cfc_bytes_per_scan(s); + + async->scan_progress += num_bytes; + if (async->scan_progress >= scan_length) { + async->scan_progress %= scan_length; + async->events |= COMEDI_CB_EOS; + } +} +EXPORT_SYMBOL_GPL(cfc_inc_scan_progress); + +/* Writes an array of data points to comedi's buffer */ +unsigned int cfc_write_array_to_buffer(struct comedi_subdevice *s, + void *data, unsigned int num_bytes) +{ + struct comedi_async *async = s->async; + unsigned int retval; + + if (num_bytes == 0) + return 0; + + retval = comedi_buf_write_alloc(s, num_bytes); + if (retval != num_bytes) { + dev_warn(s->device->class_dev, "buffer overrun\n"); + async->events |= COMEDI_CB_OVERFLOW; + return 0; + } + + comedi_buf_memcpy_to(s, 0, data, num_bytes); + comedi_buf_write_free(s, num_bytes); + cfc_inc_scan_progress(s, num_bytes); + async->events |= COMEDI_CB_BLOCK; + + return num_bytes; +} +EXPORT_SYMBOL_GPL(cfc_write_array_to_buffer); + +unsigned int cfc_read_array_from_buffer(struct comedi_subdevice *s, + void *data, unsigned int num_bytes) +{ + if (num_bytes == 0) + return 0; + + num_bytes = comedi_buf_read_alloc(s, num_bytes); + comedi_buf_memcpy_from(s, 0, data, num_bytes); + comedi_buf_read_free(s, num_bytes); + cfc_inc_scan_progress(s, num_bytes); + s->async->events |= COMEDI_CB_BLOCK; + + return num_bytes; +} +EXPORT_SYMBOL_GPL(cfc_read_array_from_buffer); + +unsigned int cfc_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_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) + s->cancel(dev, s); + + comedi_event(dev, s); + + return events; +} +EXPORT_SYMBOL_GPL(cfc_handle_events); + +static int __init comedi_fc_init_module(void) +{ + return 0; +} +module_init(comedi_fc_init_module); + +static void __exit comedi_fc_cleanup_module(void) +{ +} +module_exit(comedi_fc_cleanup_module); + +MODULE_AUTHOR("Frank Mori Hess <fmhess@users.sourceforge.net>"); +MODULE_DESCRIPTION("Shared functions for Comedi low-level drivers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/comedi_fc.h b/drivers/staging/comedi/drivers/comedi_fc.h new file mode 100644 index 00000000000..541b9371d3d --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_fc.h @@ -0,0 +1,127 @@ +/* + * comedi_fc.h + * This is a place for code driver writers wish to share between + * two or more drivers. These functions are meant to be used only + * by drivers, they are NOT part of the kcomedilib API! + * + * Author: Frank Mori Hess <fmhess@users.sourceforge.net> + * Copyright (C) 2002 Frank Mori Hess + * + * 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_FC_H +#define _COMEDI_FC_H + +#include "../comedidev.h" + +unsigned int cfc_bytes_per_scan(struct comedi_subdevice *); +void cfc_inc_scan_progress(struct comedi_subdevice *, unsigned int num_bytes); + +/* Writes an array of data points to comedi's buffer */ +unsigned int cfc_write_array_to_buffer(struct comedi_subdevice *, + void *data, unsigned int num_bytes); + +static inline unsigned int cfc_write_to_buffer(struct comedi_subdevice *s, + unsigned short data) +{ + return cfc_write_array_to_buffer(s, &data, sizeof(data)); +}; + +static inline unsigned int cfc_write_long_to_buffer(struct comedi_subdevice *s, + unsigned int data) +{ + return cfc_write_array_to_buffer(s, &data, sizeof(data)); +}; + +unsigned int cfc_read_array_from_buffer(struct comedi_subdevice *, + void *data, unsigned int num_bytes); + +unsigned int cfc_handle_events(struct comedi_device *, + struct comedi_subdevice *); + +/** + * cfc_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 cfc_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; +} + +/** + * cfc_check_trigger_is_unique() - make sure a trigger source is unique + * @src: the trigger source to check + */ +static inline int cfc_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; +} + +/** + * cfc_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 cfc_check_trigger_arg_is(unsigned int *arg, unsigned int val) +{ + if (*arg != val) { + *arg = val; + return -EINVAL; + } + return 0; +} + +/** + * cfc_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 cfc_check_trigger_arg_min(unsigned int *arg, + unsigned int val) +{ + if (*arg < val) { + *arg = val; + return -EINVAL; + } + return 0; +} + +/** + * cfc_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 cfc_check_trigger_arg_max(unsigned int *arg, + unsigned int val) +{ + if (*arg > val) { + *arg = val; + return -EINVAL; + } + return 0; +} + +#endif /* _COMEDI_FC_H */ diff --git a/drivers/staging/comedi/drivers/comedi_parport.c b/drivers/staging/comedi/drivers/comedi_parport.c new file mode 100644 index 00000000000..a4274869235 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_parport.c @@ -0,0 +1,320 @@ +/* + * 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" + +#include "comedi_fc.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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + 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_put(s, 0); + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + + comedi_event(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 00000000000..67a09aa6b72 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_test.c @@ -0,0 +1,450 @@ +/* + 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 "comedi_fc.h" +#include <linux/timer.h> + +#define N_CHANS 8 + +/* Data unique to this driver */ +struct waveform_private { + struct timer_list timer; + struct timeval 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 ai_count; /* number of conversions remaining */ + 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); + break; + case SQUARE_CHAN: + return fake_squarewave(dev, range, current_time); + break; + 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_async *async = dev->read_subdev->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int i, j; + /* all times in microsec */ + unsigned long elapsed_time; + unsigned int num_scans; + struct timeval now; + bool stopping = false; + + do_gettimeofday(&now); + + elapsed_time = + 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec - + devpriv->last.tv_usec; + devpriv->last = now; + num_scans = + (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period; + devpriv->usec_remainder = + (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period; + + if (cmd->stop_src == TRIG_COUNT) { + unsigned int remaining = cmd->stop_arg - devpriv->ai_count; + + if (num_scans >= remaining) { + /* about to finish */ + num_scans = remaining; + stopping = true; + } + } + + 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); + cfc_write_to_buffer(dev->read_subdev, sample); + } + } + + devpriv->ai_count += i; + devpriv->usec_current += elapsed_time; + devpriv->usec_current %= devpriv->usec_period; + + if (stopping) + async->events |= COMEDI_CB_EOA; + else + mod_timer(&devpriv->timer, jiffies + 1); + + comedi_event(dev, dev->read_subdev); +} + +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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW | TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_NOW) + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + nano_per_micro); + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + cmd->convert_arg * cmd->chanlist_len); + } + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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 |= cfc_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 |= cfc_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 & TRIG_RT) { + comedi_error(dev, + "commands at RT priority not supported in this driver"); + return -1; + } + + devpriv->ai_count = 0; + 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; + + do_gettimeofday(&devpriv->last); + devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period; + devpriv->usec_remainder = 0; + + devpriv->timer.expires = jiffies + 1; + 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; + + del_timer_sync(&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_WRITEABLE | 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; + + init_timer(&(devpriv->timer)); + devpriv->timer.function = waveform_ai_interrupt; + devpriv->timer.data = (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) + waveform_ai_cancel(dev, dev->read_subdev); +} + +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 00000000000..0a9c32e9db4 --- /dev/null +++ b/drivers/staging/comedi/drivers/contec_pci_dio.c @@ -0,0 +1,128 @@ +/* + 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 <linux/pci.h> + +#include "../comedidev.h" + +#define PCI_DEVICE_ID_PIO1616L 0x8172 + +/* + * 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_disable, +}; + +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, PCI_DEVICE_ID_PIO1616L) }, + { 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 00000000000..df46e0a5bad --- /dev/null +++ b/drivers/staging/comedi/drivers/dac02.c @@ -0,0 +1,172 @@ +/* + * 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) + } +}; + +struct dac02_private { + unsigned int ao_readback[2]; +}; + +/* + * 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) +{ + struct dac02_private *devpriv = dev->private; + 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]; + + devpriv->ao_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_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dac02_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return insn->n; +} + +static int dac02_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct dac02_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], 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; + s->insn_read = dac02_ao_insn_read; + + 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 00000000000..a8f6036ad82 --- /dev/null +++ b/drivers/staging/comedi/drivers/daqboard2000.c @@ -0,0 +1,815 @@ +/* + 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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "8255.h" + +#define DAQBOARD2000_FIRMWARE "daqboard2000_firmware.bin" + +#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 *daq; + void __iomem *plx; + unsigned int ao_readback[2]; +}; + +static void writeAcqScanListEntry(struct comedi_device *dev, u16 entry) +{ + struct daqboard2000_private *devpriv = dev->private; + + /* udelay(4); */ + writew(entry & 0x00ff, devpriv->daq + acqScanListFIFO); + /* udelay(4); */ + writew((entry >> 8) & 0x00ff, devpriv->daq + 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) +{ + struct daqboard2000_private *devpriv = dev->private; + unsigned int status; + + status = readw(devpriv->daq + 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) +{ + struct daqboard2000_private *devpriv = dev->private; + int gain, chan; + int ret; + int i; + + writew(DAQBOARD2000_AcqResetScanListFifo | + DAQBOARD2000_AcqResetResultsFifo | + DAQBOARD2000_AcqResetConfigPipe, devpriv->daq + 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, devpriv->daq + acqPacerClockDivLow); + writew(0, devpriv->daq + 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, + devpriv->daq + acqControl); + + ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status, + DAQBOARD2000_AcqConfigPipeFull); + if (ret) + return ret; + + writew(DAQBOARD2000_AdcPacerEnable, devpriv->daq + 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(devpriv->daq + acqResultsFIFO); + writew(DAQBOARD2000_AdcPacerDisable, devpriv->daq + acqControl); + writew(DAQBOARD2000_SeqStopScanList, devpriv->daq + acqControl); + } + + return i; +} + +static int daqboard2000_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct daqboard2000_private *devpriv = dev->private; + int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int daqboard2000_ao_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + struct daqboard2000_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int status; + + status = readw(devpriv->daq + 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) +{ + struct daqboard2000_private *devpriv = dev->private; + int chan = CR_CHAN(insn->chanspec); + int ret; + int i; + + for (i = 0; i < insn->n; i++) { +#if 0 + /* + * OK, since it works OK without enabling the DAC's, + * let's keep it as simple as possible... + */ + writew((chan + 2) * 0x0010 | 0x0001, + devpriv->daq + dacControl); + udelay(1000); +#endif + writew(data[i], devpriv->daq + dacSetting(chan)); + + ret = comedi_timeout(dev, s, insn, daqboard2000_ao_eoc, 0); + if (ret) + return ret; + + devpriv->ao_readback[chan] = data[i]; +#if 0 + /* + * Since we never enabled the DAC's, we don't need + * to disable it... + */ + writew((chan + 2) * 0x0010 | 0x0000, + devpriv->daq + dacControl); + udelay(1000); +#endif + } + + return i; +} + +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) +{ + struct daqboard2000_private *devpriv = dev->private; + int result = 0; + int i; + int cpld; + + /* timeout after 50 tries -> 5ms */ + for (i = 0; i < 50; i++) { + cpld = readw(devpriv->daq + 0x1000); + if ((cpld & mask) == mask) { + result = 1; + break; + } + udelay(100); + } + udelay(5); + return result; +} + +static int daqboard2000_writeCPLD(struct comedi_device *dev, int data) +{ + struct daqboard2000_private *devpriv = dev->private; + int result = 0; + + udelay(10); + writew(data, devpriv->daq + 0x1000); + if ((readw(devpriv->daq + 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) +{ + struct daqboard2000_private *devpriv = dev->private; + + /* Disable hardware triggers */ + udelay(2); + writew(DAQBOARD2000_TrigAnalog | DAQBOARD2000_TrigDisable, + devpriv->daq + trigControl); + udelay(2); + writew(DAQBOARD2000_TrigTTL | DAQBOARD2000_TrigDisable, + devpriv->daq + trigControl); + + /* Stop the scan list FIFO from loading the configuration pipe */ + udelay(2); + writew(DAQBOARD2000_SeqStopScanList, devpriv->daq + acqControl); + + /* Stop the pacer clock */ + udelay(2); + writew(DAQBOARD2000_AdcPacerDisable, devpriv->daq + acqControl); + + /* Stop the input dma (abort channel 1) */ + daqboard2000_adcStopDmaTransfer(dev); +} + +static void daqboard2000_activateReferenceDacs(struct comedi_device *dev) +{ + struct daqboard2000_private *devpriv = dev->private; + unsigned int val; + int timeout; + + /* Set the + reference dac value in the FPGA */ + writew(0x80 | DAQBOARD2000_PosRefDacSelect, devpriv->daq + refDacs); + for (timeout = 0; timeout < 20; timeout++) { + val = readw(devpriv->daq + dacControl); + if ((val & DAQBOARD2000_RefBusy) == 0) + break; + udelay(2); + } + + /* Set the - reference dac value in the FPGA */ + writew(0x80 | DAQBOARD2000_NegRefDacSelect, devpriv->daq + refDacs); + for (timeout = 0; timeout < 20; timeout++) { + val = readw(devpriv->daq + 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(int dir, int port, int data, + unsigned long ioaddr) +{ + void __iomem *mmio_base = (void __iomem *)ioaddr; + + if (dir) { + writew(data, mmio_base + port * 2); + return 0; + } else { + return readw(mmio_base + 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); + devpriv->daq = pci_ioremap_bar(pcidev, 2); + if (!devpriv->plx || !devpriv->daq) + 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_read = daqboard2000_ao_insn_read; + s->insn_write = daqboard2000_ao_insn_write; + s->range_table = &range_bipolar10; + + s = &dev->subdevices[2]; + result = subdev_8255_init(dev, s, daqboard2000_8255_cb, + (unsigned long)(devpriv->daq + dioP2ExpansionIO8Bit)); + if (result) + return result; + + return 0; +} + +static void daqboard2000_detach(struct comedi_device *dev) +{ + struct daqboard2000_private *devpriv = dev->private; + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->daq) + iounmap(devpriv->daq); + if (devpriv->plx) + iounmap(devpriv->plx); + } + comedi_pci_disable(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"); +MODULE_FIRMWARE(DAQBOARD2000_FIRMWARE); diff --git a/drivers/staging/comedi/drivers/das08.c b/drivers/staging/comedi/drivers/das08.c new file mode 100644 index 00000000000..c5e352fb555 --- /dev/null +++ b/drivers/staging/comedi/drivers/das08.c @@ -0,0 +1,580 @@ +/* + * comedi/drivers/das08.c + * comedi driver 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. + */ + +/* + * Driver: das08 + * Description: DAS-08 compatible boards + * Devices: various, see das08_isa, das08_cs, and das08_pci drivers + * Author: Warren Jasper, ds, Frank Hess + * Updated: Fri, 31 Aug 2012 19:19:06 +0100 + * Status: works + * + * This driver is used by the das08_isa, das08_cs, and das08_pci + * drivers to provide the common support for the DAS-08 hardware. + * + * The driver doesn't support asynchronous commands, since the + * cheap das08 hardware doesn't really support them. + */ + +#include <linux/module.h> + +#include "../comedidev.h" + +#include "8255.h" +#include "8253.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 = comedi_board(dev); + 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 { + comedi_error(dev, "bug! unknown ai encoding"); + 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 = comedi_board(dev); + struct das08_private_struct *devpriv = dev->private; + 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); + } + devpriv->ao_readback[chan] = data; +} + +static void das08_ao_initialize(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int n; + unsigned int data; + + data = s->maxdata / 2; /* should be about 0 volts */ + for (n = 0; n < s->n_chan; n++) + das08_ao_set_data(dev, n, data); +} + +static int das08_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int n; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) + das08_ao_set_data(dev, chan, *data); + + return n; +} + +static int das08_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct das08_private_struct *devpriv = dev->private; + unsigned int n; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_readback[chan]; + + return n; +} + +static void i8254_initialize(struct comedi_device *dev) +{ + const struct das08_board_struct *thisboard = comedi_board(dev); + unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset; + unsigned int mode = I8254_MODE0 | I8254_BINARY; + int i; + + for (i = 0; i < 3; ++i) + i8254_set_mode(i8254_iobase, 0, i, mode); +} + +static int das08_counter_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct das08_board_struct *thisboard = comedi_board(dev); + unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset; + int chan = insn->chanspec; + + data[0] = i8254_read(i8254_iobase, 0, chan); + return 1; +} + +static int das08_counter_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct das08_board_struct *thisboard = comedi_board(dev); + unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset; + int chan = insn->chanspec; + + i8254_write(i8254_iobase, 0, chan, data[0]); + return 1; +} + +static int das08_counter_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct das08_board_struct *thisboard = comedi_board(dev); + unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset; + int chan = insn->chanspec; + + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + i8254_set_mode(i8254_iobase, 0, chan, data[1]); + break; + case INSN_CONFIG_8254_READ_STATUS: + data[1] = i8254_status(i8254_iobase, 0, chan); + break; + default: + return -EINVAL; + break; + } + return 2; +} + +int das08_common_attach(struct comedi_device *dev, unsigned long iobase) +{ + const struct das08_board_struct *thisboard = comedi_board(dev); + struct das08_private_struct *devpriv = dev->private; + struct comedi_subdevice *s; + int ret; + + 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_winsn; + s->insn_read = das08_ao_rinsn; + das08_ao_initialize(dev, s); + } 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 | SDF_READABLE; + 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, + dev->iobase + thisboard->i8255_offset); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[5]; + /* 8254 */ + if (thisboard->i8254_offset != 0) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 3; + s->maxdata = 0xFFFF; + s->insn_read = das08_counter_read; + s->insn_write = das08_counter_write; + s->insn_config = das08_counter_config; + i8254_initialize(dev); + } 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 00000000000..18cc170facd --- /dev/null +++ b/drivers/staging/comedi/drivers/das08.h @@ -0,0 +1,52 @@ +/* + 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; + unsigned int ao_readback[2]; /* assume 2 AO channels */ +}; + +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 00000000000..f3ccc2ce6d4 --- /dev/null +++ b/drivers/staging/comedi/drivers/das08_cs.c @@ -0,0 +1,117 @@ +/* + 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 "../comedidev.h" + +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.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 00000000000..4fb03d3852d --- /dev/null +++ b/drivers/staging/comedi/drivers/das08_isa.c @@ -0,0 +1,205 @@ +/* + * 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] + * (ComputerBoards) DAS08-PGM [das08-pgm] + * (ComputerBoards) DAS08-PGH [das08-pgh] + * (ComputerBoards) DAS08-PGL [das08-pgl] + * (ComputerBoards) DAS08-AOH [das08-aoh] + * (ComputerBoards) DAS08-AOL [das08-aol] + * (ComputerBoards) DAS08-AOM [das08-aom] + * (ComputerBoards) DAS08/JR-AO [das08/jr-ao] + * (ComputerBoards) DAS08/JR-16-AO [das08jr-16-ao] + * (ComputerBoards) PC104-DAS08 [pc104-das08] + * (ComputerBoards) 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 = comedi_board(dev); + 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 00000000000..d94af09151b --- /dev/null +++ b/drivers/staging/comedi/drivers/das08_pci.c @@ -0,0 +1,108 @@ +/* + * 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 <linux/pci.h> + +#include "../comedidev.h" + +#include "das08.h" + +#define PCI_DEVICE_ID_PCIDAS08 0x0029 + +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_disable, +}; + +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, PCI_DEVICE_ID_PCIDAS08) }, + { 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 00000000000..2feecf199f2 --- /dev/null +++ b/drivers/staging/comedi/drivers/das16.c @@ -0,0 +1,1259 @@ +/* + * 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] + * (Keithley Metrabyte) DAS-16G [das-16g] + * (Keithley Metrabyte) DAS-16F [das-16f] + * (Keithley Metrabyte) DAS-1201 [das-1201] + * (Keithley Metrabyte) DAS-1202 [das-1202] + * (Keithley Metrabyte) DAS-1401 [das-1401] + * (Keithley Metrabyte) DAS-1402 [das-1402] + * (Keithley Metrabyte) DAS-1601 [das-1601] + * (Keithley Metrabyte) DAS-1602 [das-1602] + * (ComputerBoards) PC104-DAS16/JR [pc104-das16jr] + * (ComputerBoards) PC104-DAS16JR/16 [pc104-das16jr/16] + * (ComputerBoards) CIO-DAS16 [cio-das16] + * (ComputerBoards) CIO-DAS16F [cio-das16/f] + * (ComputerBoards) CIO-DAS16/JR [cio-das16/jr] + * (ComputerBoards) CIO-DAS16JR/16 [cio-das16jr/16] + * (ComputerBoards) CIO-DAS1401/12 [cio-das1401/12] + * (ComputerBoards) CIO-DAS1402/12 [cio-das1402/12] + * (ComputerBoards) CIO-DAS1402/16 [cio-das1402/16] + * (ComputerBoards) CIO-DAS1601/12 [cio-das1601/12] + * (ComputerBoards) CIO-DAS1602/12 [cio-das1602/12] + * (ComputerBoards) CIO-DAS1602/16 [cio-das1602/16] + * (ComputerBoards) 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/delay.h> +#include <linux/pci.h> +#include <linux/interrupt.h> + +#include <asm/dma.h> + +#include "../comedidev.h" + +#include "8253.h" +#include "8255.h" +#include "comedi_fc.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 { + unsigned int clockbase; + unsigned int ctrl_reg; + unsigned long adc_byte_count; + unsigned int divisor1; + unsigned int divisor2; + unsigned int dma_chan; + uint16_t *dma_buffer[2]; + dma_addr_t dma_buffer_addr[2]; + unsigned int current_buffer; + unsigned int dma_transfer_size; + struct comedi_lrange *user_ai_range_table; + struct comedi_lrange *user_ao_range_table; + struct timer_list timer; + short timer_running; + unsigned long extra_iobase; + unsigned int can_burst:1; +}; + +static void das16_ai_enable(struct comedi_device *dev, + unsigned int mode, unsigned int src) +{ + struct das16_private_struct *devpriv = dev->private; + + devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE | + DAS16_CTRL_DMAE | + DAS16_CTRL_PACING_MASK); + devpriv->ctrl_reg |= mode; + + if (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); +} + +static void das16_ai_disable(struct comedi_device *dev) +{ + struct das16_private_struct *devpriv = dev->private; + + /* 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); +} + +/* the pc104-das16jr (at least) has problems if the dma + transfer is interrupted in the middle of transferring + a 16 bit sample, so this function takes care to get + an even transfer count after disabling dma + channel. +*/ +static int disable_dma_on_even(struct comedi_device *dev) +{ + struct das16_private_struct *devpriv = dev->private; + int residue; + int i; + static const int disable_limit = 100; + static const int enable_timeout = 100; + + disable_dma(devpriv->dma_chan); + residue = get_dma_residue(devpriv->dma_chan); + for (i = 0; i < disable_limit && (residue % 2); ++i) { + int j; + enable_dma(devpriv->dma_chan); + for (j = 0; j < enable_timeout; ++j) { + int new_residue; + udelay(2); + new_residue = get_dma_residue(devpriv->dma_chan); + if (new_residue != residue) + break; + } + disable_dma(devpriv->dma_chan); + residue = get_dma_residue(devpriv->dma_chan); + } + if (i == disable_limit) { + dev_err(dev->class_dev, + "failed to get an even dma transfer, could be trouble\n"); + } + return residue; +} + +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; + unsigned long spin_flags; + unsigned long dma_flags; + int num_bytes, residue; + int buffer_index; + + spin_lock_irqsave(&dev->spinlock, spin_flags); + if (!(devpriv->ctrl_reg & DAS16_CTRL_DMAE)) { + spin_unlock_irqrestore(&dev->spinlock, spin_flags); + return; + } + + dma_flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma_chan); + residue = disable_dma_on_even(dev); + + /* figure out how many points to read */ + if (residue > devpriv->dma_transfer_size) { + dev_err(dev->class_dev, "residue > transfer size!\n"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + num_bytes = 0; + } else + num_bytes = devpriv->dma_transfer_size - residue; + + if (cmd->stop_src == TRIG_COUNT && + num_bytes >= devpriv->adc_byte_count) { + num_bytes = devpriv->adc_byte_count; + async->events |= COMEDI_CB_EOA; + } + + buffer_index = devpriv->current_buffer; + devpriv->current_buffer = (devpriv->current_buffer + 1) % 2; + devpriv->adc_byte_count -= num_bytes; + + /* re-enable dma */ + if ((async->events & COMEDI_CB_EOA) == 0) { + set_dma_addr(devpriv->dma_chan, + devpriv->dma_buffer_addr[devpriv->current_buffer]); + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + } + release_dma_lock(dma_flags); + + spin_unlock_irqrestore(&dev->spinlock, spin_flags); + + cfc_write_array_to_buffer(s, + devpriv->dma_buffer[buffer_index], num_bytes); + + cfc_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; + + das16_interrupt(dev); + + if (devpriv->timer_running) + mod_timer(&devpriv->timer, jiffies + timer_period()); +} + +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 = comedi_board(dev); + 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + + trig_mask = TRIG_FOLLOW; + if (devpriv->can_burst) + trig_mask |= TRIG_TIMER | TRIG_EXT; + err |= cfc_check_trigger_src(&cmd->scan_begin_src, trig_mask); + + trig_mask = TRIG_TIMER | TRIG_EXT; + if (devpriv->can_burst) + trig_mask |= TRIG_NOW; + err |= cfc_check_trigger_src(&cmd->convert_src, trig_mask); + + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + /* check against maximum frequency */ + if (cmd->scan_begin_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ai_speed * cmd->chanlist_len); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + + if (cmd->stop_src == TRIG_NONE) + err |= cfc_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; + i8253_cascade_ns_to_timer(devpriv->clockbase, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + i8253_cascade_ns_to_timer(devpriv->clockbase, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_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, + int rounding_flags) +{ + struct das16_private_struct *devpriv = dev->private; + unsigned long timer_base = dev->iobase + DAS16_TIMER_BASE_REG; + + i8253_cascade_ns_to_timer(devpriv->clockbase, + &devpriv->divisor1, &devpriv->divisor2, + &ns, rounding_flags); + + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + i8254_write(timer_base, 0, 1, devpriv->divisor1); + i8254_write(timer_base, 0, 2, devpriv->divisor2); + + return ns; +} + +static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct das16_board *board = comedi_board(dev); + struct das16_private_struct *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int byte; + unsigned long flags; + int range; + + if (cmd->flags & TRIG_RT) { + dev_err(dev->class_dev, + "isa dma transfers cannot be performed with TRIG_RT, aborting\n"); + return -1; + } + + devpriv->adc_byte_count = cmd->stop_arg * cfc_bytes_per_scan(s); + + if (devpriv->can_burst) + outb(DAS1600_CONV_DISABLE, dev->iobase + DAS1600_CONV_REG); + + /* set scan limits */ + byte = CR_CHAN(cmd->chanlist[0]); + byte |= CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]) << 4; + outb(byte, dev->iobase + DAS16_MUX_REG); + + /* set gain (this is also burst rate register but according to + * computer boards manual, burst rate does nothing, even on + * keithley cards) */ + if (board->ai_pg != das16_pg_none) { + range = CR_RANGE(cmd->chanlist[0]); + outb((das16_gainlists[board->ai_pg])[range], + dev->iobase + DAS16_GAIN_REG); + } + + /* set counter mode and counts */ + cmd->convert_arg = + das16_set_pacer(dev, cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* 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 */ + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma_chan); + devpriv->current_buffer = 0; + set_dma_addr(devpriv->dma_chan, + devpriv->dma_buffer_addr[devpriv->current_buffer]); + devpriv->dma_transfer_size = DAS16_DMA_SIZE; + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + release_dma_lock(flags); + + /* set up interrupt */ + devpriv->timer_running = 1; + devpriv->timer.expires = jiffies + timer_period(); + add_timer(&devpriv->timer); + + das16_ai_enable(dev, DAS16_CTRL_DMAE, cmd->convert_src); + + if (devpriv->can_burst) + outb(0, dev->iobase + DAS1600_CONV_REG); + + return 0; +} + +static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct das16_private_struct *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + + das16_ai_disable(dev); + disable_dma(devpriv->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 int i, num_samples = num_bytes / sizeof(short); + unsigned short *data = array; + + 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) +{ + const struct das16_board *board = comedi_board(dev); + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int ret; + int i; + + das16_ai_disable(dev); + + /* set multiplexer */ + outb(chan | (chan << 4), dev->iobase + DAS16_MUX_REG); + + /* set gain */ + if (board->ai_pg != das16_pg_none) { + outb((das16_gainlists[board->ai_pg])[range], + dev->iobase + DAS16_GAIN_REG); + } + + 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); + unsigned int val; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + 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 = comedi_board(dev); + 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); + outb(0, dev->iobase + DAS16_TIMER_BASE_REG + i8254_control_reg); +} + +static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct das16_board *board = comedi_board(dev); + struct das16_private_struct *devpriv; + struct comedi_subdevice *s; + struct comedi_lrange *lrange; + struct comedi_krange *krange; + unsigned int dma_chan = it->options[2]; + 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 */ + if (devpriv->can_burst) { + status = inb(dev->iobase + DAS1600_STATUS_REG); + + if (status & DAS1600_STATUS_CLK_10MHZ) + devpriv->clockbase = I8254_OSC_BASE_10MHZ; + else + devpriv->clockbase = I8254_OSC_BASE_1MHZ; + } else { + if (it->options[3]) + devpriv->clockbase = I8254_OSC_BASE_1MHZ / + it->options[3]; + else + devpriv->clockbase = I8254_OSC_BASE_1MHZ; + } + + /* initialize dma */ + if (dma_chan == 1 || dma_chan == 3) { + unsigned long flags; + int i; + + if (request_dma(dma_chan, dev->board_name)) { + dev_err(dev->class_dev, + "failed to request dma channel %i\n", + dma_chan); + return -EINVAL; + } + devpriv->dma_chan = dma_chan; + + /* allocate dma buffers */ + for (i = 0; i < 2; i++) { + void *p; + + p = pci_alloc_consistent(NULL, DAS16_DMA_SIZE, + &devpriv->dma_buffer_addr[i]); + if (!p) + return -ENOMEM; + devpriv->dma_buffer[i] = p; + } + + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + set_dma_mode(devpriv->dma_chan, DMA_MODE_READ); + release_dma_lock(flags); + + init_timer(&devpriv->timer); + devpriv->timer.function = das16_timer_interrupt; + devpriv->timer.data = (unsigned long)dev; + } + + /* get any user-defined input range */ + if (board->ai_pg == das16_pg_none && + (it->options[4] || it->options[5])) { + /* allocate single-range range table */ + lrange = kzalloc(sizeof(*lrange) + sizeof(*krange), GFP_KERNEL); + if (!lrange) + return -ENOMEM; + + /* initialize ai range */ + devpriv->user_ai_range_table = lrange; + lrange->length = 1; + krange = devpriv->user_ai_range_table->range; + krange->min = it->options[4]; + krange->max = it->options[5]; + krange->flags = UNIT_volt; + } + + /* get any user-defined output range */ + if (it->options[6] || it->options[7]) { + /* allocate single-range range table */ + lrange = kzalloc(sizeof(*lrange) + sizeof(*krange), GFP_KERNEL); + if (!lrange) + return -ENOMEM; + + /* initialize ao range */ + devpriv->user_ao_range_table = lrange; + lrange->length = 1; + krange = devpriv->user_ao_range_table->range; + krange->min = it->options[6]; + krange->max = it->options[7]; + krange->flags = UNIT_volt; + } + + 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; + if (devpriv->user_ai_range_table) { /* user defined ai range */ + s->range_table = devpriv->user_ai_range_table; + } else if (status & DAS16_STATUS_UNIPOLAR) { + s->range_table = das16_ai_uni_lranges[board->ai_pg]; + } else { + s->range_table = das16_ai_bip_lranges[board->ai_pg]; + } + s->insn_read = das16_ai_insn_read; + if (devpriv->dma_chan) { + 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 = devpriv->user_ao_range_table; + s->insn_write = das16_ao_insn_write; + } 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, + dev->iobase + 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 = comedi_board(dev); + struct das16_private_struct *devpriv = dev->private; + int i; + + if (devpriv) { + if (dev->iobase) + das16_reset(dev); + + for (i = 0; i < 2; i++) { + if (devpriv->dma_buffer[i]) + pci_free_consistent(NULL, DAS16_DMA_SIZE, + devpriv->dma_buffer[i], + devpriv-> + dma_buffer_addr[i]); + } + if (devpriv->dma_chan) + free_dma(devpriv->dma_chan); + kfree(devpriv->user_ai_range_table); + kfree(devpriv->user_ao_range_table); + + 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 00000000000..ec039fbff0f --- /dev/null +++ b/drivers/staging/comedi/drivers/das16m1.c @@ -0,0 +1,648 @@ +/* + 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/interrupt.h> +#include "../comedidev.h" + +#include "8255.h" +#include "8253.h" +#include "comedi_fc.h" + +#define DAS16M1_SIZE 16 +#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_FIRST_CNTRL 0xb +#define TOTAL_CLEAR 0x30 +#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 { + unsigned int control_state; + volatile 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 int divisor1; /* divides master clock to obtain conversion speed */ + unsigned int divisor2; /* divides master clock to obtain conversion speed */ + 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) +{ + struct das16m1_private_struct *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 1000); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + } + + if (err) + return 3; + + /* step 4: fix up arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_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 void das16m1_set_pacer(struct comedi_device *dev) +{ + struct das16m1_private_struct *devpriv = dev->private; + unsigned long timer_base = dev->iobase + DAS16M1_8254_SECOND; + + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + + i8254_write(timer_base, 0, 1, devpriv->divisor1); + i8254_write(timer_base, 0, 2, devpriv->divisor2); +} + +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 long timer_base = dev->iobase + DAS16M1_8254_FIRST; + 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) */ + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_write(timer_base, 0, 1, 0); + /* remember current reading of counter so we know when counter has + * actually been loaded */ + devpriv->initial_hw_count = i8254_read(timer_base, 0, 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) { + das16m1_set_pacer(dev); + 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 = i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 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); + cfc_write_array_to_buffer(s, devpriv->ai_buffer, + num_samples * sizeof(short)); + 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_EOA | COMEDI_CB_ERROR; + comedi_error(dev, "fifo overflow"); + } + + cfc_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 s->async->buf_write_count - s->async->buf_read_count; +} + +static irqreturn_t das16m1_interrupt(int irq, void *d) +{ + int status; + struct comedi_device *dev = d; + + if (!dev->attached) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + /* prevent race with comedi_poll() */ + spin_lock(&dev->spinlock); + + status = inb(dev->iobase + DAS16M1_CS); + + if ((status & (IRQDATA | OVRUN)) == 0) { + comedi_error(dev, "spurious interrupt"); + 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], DAS16M1_SIZE); + 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]; + } + + 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 | SDF_READABLE; + 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, devpriv->extra_iobase); + if (ret) + return ret; + + /* disable upper half of hardware conversion counter so it doesn't mess with us */ + outb(TOTAL_CLEAR, dev->iobase + DAS16M1_8254_FIRST_CNTRL); + + /* 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 && devpriv->extra_iobase) + release_region(devpriv->extra_iobase, DAS16M1_SIZE2); + 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 00000000000..859519026c4 --- /dev/null +++ b/drivers/staging/comedi/drivers/das1800.c @@ -0,0 +1,1608 @@ +/* + 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 <asm/dma.h> + +#include "8253.h" +#include "comedi_fc.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 { + volatile unsigned int count; /* number of data points left to be taken */ + unsigned int divisor1; /* value to load into board's counter 1 for timed conversions */ + unsigned int divisor2; /* value to load into board's counter 2 for timed conversions */ + 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; + unsigned int dma0; /* dma channels used */ + unsigned int dma1; + volatile unsigned int dma_current; /* dma channel currently in use */ + uint16_t *ai_buf0; /* pointers to dma buffers */ + uint16_t *ai_buf1; + uint16_t *dma_current_buf; /* pointer to dma buffer currently being used */ + unsigned int dma_transfer_size; /* size of transfer currently used, in bytes */ + 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 = comedi_board(dev); + + 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; + int numPoints = 0; /* number of points to read */ + struct comedi_cmd *cmd = &s->async->cmd; + + numPoints = FIFO_SIZE / 2; + /* if we only need some of the points */ + if (cmd->stop_src == TRIG_COUNT && devpriv->count < numPoints) + numPoints = devpriv->count; + insw(dev->iobase + DAS1800_FIFO, devpriv->ai_buf0, numPoints); + munge_data(dev, devpriv->ai_buf0, numPoints); + cfc_write_array_to_buffer(s, devpriv->ai_buf0, + numPoints * sizeof(devpriv->ai_buf0[0])); + if (cmd->stop_src == TRIG_COUNT) + devpriv->count -= numPoints; + return; +} + +static void das1800_handle_fifo_not_empty(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das1800_private *devpriv = dev->private; + unsigned short dpnt; + int unipolar; + struct comedi_cmd *cmd = &s->async->cmd; + + unipolar = inb(dev->iobase + DAS1800_CONTROL_C) & UB; + + while (inb(dev->iobase + DAS1800_STATUS) & FNE) { + if (cmd->stop_src == TRIG_COUNT && devpriv->count == 0) + break; + dpnt = inw(dev->iobase + DAS1800_FIFO); + /* convert to unsigned type if we are in a bipolar mode */ + if (!unipolar) + ; + dpnt = munge_bipolar_sample(dev, dpnt); + cfc_write_to_buffer(s, dpnt); + if (cmd->stop_src == TRIG_COUNT) + devpriv->count--; + } + + return; +} + +/* Utility function used by das1800_flush_dma() and das1800_handle_dma(). + * Assumes dma lock is held */ +static void das1800_flush_dma_channel(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int channel, uint16_t *buffer) +{ + struct das1800_private *devpriv = dev->private; + unsigned int num_bytes, num_samples; + struct comedi_cmd *cmd = &s->async->cmd; + + disable_dma(channel); + + /* clear flip-flop to make sure 2-byte registers + * get set correctly */ + clear_dma_ff(channel); + + /* figure out how many points to read */ + num_bytes = devpriv->dma_transfer_size - get_dma_residue(channel); + num_samples = num_bytes / sizeof(short); + + /* if we only need some of the points */ + if (cmd->stop_src == TRIG_COUNT && devpriv->count < num_samples) + num_samples = devpriv->count; + + munge_data(dev, buffer, num_samples); + cfc_write_array_to_buffer(s, buffer, num_bytes); + if (cmd->stop_src == TRIG_COUNT) + devpriv->count -= num_samples; + + return; +} + +/* 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; + unsigned long flags; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + flags = claim_dma_lock(); + das1800_flush_dma_channel(dev, s, devpriv->dma_current, + devpriv->dma_current_buf); + + if (dual_dma) { + /* switch to other channel and flush it */ + if (devpriv->dma_current == devpriv->dma0) { + devpriv->dma_current = devpriv->dma1; + devpriv->dma_current_buf = devpriv->ai_buf1; + } else { + devpriv->dma_current = devpriv->dma0; + devpriv->dma_current_buf = devpriv->ai_buf0; + } + das1800_flush_dma_channel(dev, s, devpriv->dma_current, + devpriv->dma_current_buf); + } + + release_dma_lock(flags); + + /* get any remaining samples in fifo */ + das1800_handle_fifo_not_empty(dev, s); + + return; +} + +static void das1800_handle_dma(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int status) +{ + struct das1800_private *devpriv = dev->private; + unsigned long flags; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + flags = claim_dma_lock(); + das1800_flush_dma_channel(dev, s, devpriv->dma_current, + devpriv->dma_current_buf); + /* re-enable dma channel */ + set_dma_addr(devpriv->dma_current, + virt_to_bus(devpriv->dma_current_buf)); + set_dma_count(devpriv->dma_current, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_current); + release_dma_lock(flags); + + 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) { + /* read data from the other channel next time */ + if (devpriv->dma_current == devpriv->dma0) { + devpriv->dma_current = devpriv->dma1; + devpriv->dma_current_buf = devpriv->ai_buf1; + } else { + devpriv->dma_current = devpriv->dma0; + devpriv->dma_current_buf = devpriv->ai_buf0; + } + } + } + + return; +} + +static int das1800_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct das1800_private *devpriv = dev->private; + + 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 */ + if (devpriv->dma0) + disable_dma(devpriv->dma0); + if (devpriv->dma1) + disable_dma(devpriv->dma1); + 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); + } + + async->events |= COMEDI_CB_BLOCK; + /* if the card's fifo has overflowed */ + if (status & OVF) { + /* clear OVF interrupt bit */ + outb(CLEAR_INTR_MASK & ~OVF, dev->iobase + DAS1800_STATUS); + comedi_error(dev, "DAS1800 FIFO overflow"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_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 && devpriv->count == 0) { /* stop_src TRIG_COUNT */ + async->events |= COMEDI_CB_EOA; + } + + cfc_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 s->async->buf_write_count - s->async->buf_read_count; +} + +static irqreturn_t das1800_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int status; + + if (!dev->attached) { + comedi_error(dev, "premature interrupt"); + 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 & TRIG_ROUND_MASK) { + case TRIG_ROUND_NEAREST: + default: + micro_sec = (convert_arg + 500) / 1000; + break; + case TRIG_ROUND_DOWN: + micro_sec = convert_arg / 1000; + break; + case TRIG_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 = comedi_board(dev); + struct das1800_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + thisboard->ai_speed); + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_COUNT: + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_NONE: + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_5MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &cmd->convert_arg, cmd->flags); + if (arg != cmd->convert_arg) + err++; + } else if (cmd->convert_src == TRIG_TIMER) { + /* we are in burst mode */ + arg = cmd->convert_arg; + cmd->convert_arg = burst_convert_arg(cmd->convert_arg, + cmd->flags); + if (arg != cmd->convert_arg) + err++; + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->chanlist_len; + if (arg > cmd->scan_begin_arg) { + cmd->scan_begin_arg = arg; + err++; + } + + arg = cmd->scan_begin_arg; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_5MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &cmd->scan_begin_arg, + cmd->flags); + if (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 |= 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 void das1800_setup_counters(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct das1800_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + DAS1800_COUNTER; + + /* 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) { + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + + i8254_write(timer_base, 0, 1, devpriv->divisor1); + i8254_write(timer_base, 0, 2, devpriv->divisor2); + } + + /* setup counter 0 for 'about triggering' */ + if (cmd->stop_src == TRIG_EXT) { + i8254_set_mode(timer_base, 0, 0, I8254_MODE0 | I8254_BINARY); + + i8254_write(timer_base, 0, 0, 1); + } +} + +/* utility function that suggests a dma transfer size based on the conversion period 'ns' */ +static unsigned int suggest_transfer_size(const struct comedi_cmd *cmd) +{ + unsigned int size = DMA_BUF_SIZE; + static const int sample_size = 2; /* size in bytes of one sample from board */ + unsigned int fill_time = 300000000; /* target time in nanoseconds for filling dma buffer */ + unsigned int max_size; /* maximum size we will allow for a transfer */ + + /* make dma buffer fill in 0.3 seconds for timed modes */ + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: /* not in burst mode */ + if (cmd->convert_src == TRIG_TIMER) + size = (fill_time / cmd->convert_arg) * sample_size; + break; + case TRIG_TIMER: + size = (fill_time / (cmd->scan_begin_arg * cmd->chanlist_len)) * + sample_size; + break; + default: + size = DMA_BUF_SIZE; + break; + } + + /* set a minimum and maximum size allowed */ + max_size = DMA_BUF_SIZE; + /* if we are taking limited number of conversions, limit transfer size to that */ + if (cmd->stop_src == TRIG_COUNT && + cmd->stop_arg * cmd->chanlist_len * sample_size < max_size) + max_size = cmd->stop_arg * cmd->chanlist_len * sample_size; + + if (size > max_size) + size = max_size; + if (size < sample_size) + size = sample_size; + + return size; +} + +/* sets up dma */ +static void setup_dma(struct comedi_device *dev, const struct comedi_cmd *cmd) +{ + struct das1800_private *devpriv = dev->private; + unsigned long lock_flags; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + if ((devpriv->irq_dma_bits & DMA_ENABLED) == 0) + return; + + /* determine a reasonable dma transfer size */ + devpriv->dma_transfer_size = suggest_transfer_size(cmd); + lock_flags = claim_dma_lock(); + disable_dma(devpriv->dma0); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma0); + set_dma_addr(devpriv->dma0, virt_to_bus(devpriv->ai_buf0)); + /* set appropriate size of transfer */ + set_dma_count(devpriv->dma0, devpriv->dma_transfer_size); + devpriv->dma_current = devpriv->dma0; + devpriv->dma_current_buf = devpriv->ai_buf0; + enable_dma(devpriv->dma0); + /* set up dual dma if appropriate */ + if (dual_dma) { + disable_dma(devpriv->dma1); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma1); + set_dma_addr(devpriv->dma1, virt_to_bus(devpriv->ai_buf1)); + /* set appropriate size of transfer */ + set_dma_count(devpriv->dma1, devpriv->dma_transfer_size); + enable_dma(devpriv->dma1); + } + release_dma_lock(lock_flags); + + return; +} + +/* 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); + + return; +} + +/* 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 TRIG_WAKE_EOS, or TRIG_RT + * (because dma in handler is unsafe at hard real-time priority) */ + if (cmd->flags & (TRIG_WAKE_EOS | TRIG_RT)) + devpriv->irq_dma_bits &= ~DMA_ENABLED; + else + devpriv->irq_dma_bits |= devpriv->dma_bits; + /* interrupt on end of conversion for TRIG_WAKE_EOS */ + if (cmd->flags & TRIG_WAKE_EOS) { + /* interrupt fifo not empty */ + devpriv->irq_dma_bits &= ~FIMD; + } else { + /* interrupt fifo half full */ + devpriv->irq_dma_bits |= FIMD; + } + /* determine how many conversions we need */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + + 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); + das1800_setup_counters(dev, cmd); + setup_dma(dev, cmd); + 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 = comedi_board(dev); + 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) { + comedi_error(dev, "timeout"); + 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 = comedi_board(dev); + 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 int das1800_init_dma(struct comedi_device *dev, unsigned int dma0, + unsigned int dma1) +{ + struct das1800_private *devpriv = dev->private; + unsigned long flags; + + /* need an irq to do dma */ + if (dev->irq && dma0) { + /* encode dma0 and dma1 into 2 digit hexadecimal for switch */ + switch ((dma0 & 0x7) | (dma1 << 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: + dev_err(dev->class_dev, + "only supports dma channels 5 through 7\n"); + dev_err(dev->class_dev, + "Dual dma only allows the following combinations:\n"); + dev_err(dev->class_dev, + "dma 5,6 / 6,7 / or 7,5\n"); + return -EINVAL; + break; + } + if (request_dma(dma0, dev->driver->driver_name)) { + dev_err(dev->class_dev, + "failed to allocate dma channel %i\n", dma0); + return -EINVAL; + } + devpriv->dma0 = dma0; + devpriv->dma_current = dma0; + if (dma1) { + if (request_dma(dma1, dev->driver->driver_name)) { + dev_err(dev->class_dev, + "failed to allocate dma channel %i\n", + dma1); + return -EINVAL; + } + devpriv->dma1 = dma1; + } + devpriv->ai_buf0 = kmalloc(DMA_BUF_SIZE, GFP_KERNEL | GFP_DMA); + if (devpriv->ai_buf0 == NULL) + return -ENOMEM; + devpriv->dma_current_buf = devpriv->ai_buf0; + if (dma1) { + devpriv->ai_buf1 = + kmalloc(DMA_BUF_SIZE, GFP_KERNEL | GFP_DMA); + if (devpriv->ai_buf1 == NULL) + return -ENOMEM; + } + flags = claim_dma_lock(); + disable_dma(devpriv->dma0); + set_dma_mode(devpriv->dma0, DMA_MODE_READ); + if (dma1) { + disable_dma(devpriv->dma1); + set_dma_mode(devpriv->dma1, DMA_MODE_READ); + } + release_dma_lock(flags); + } + return 0; +} + +static int das1800_probe(struct comedi_device *dev) +{ + int id; + int board; + + id = (inb(dev->iobase + DAS1800_DIGITAL) >> 4) & 0xf; /* get id bits */ + board = ((struct das1800_board *)dev->board_ptr) - das1800_boards; + + switch (id) { + case 0x3: + if (board == das1801st_da || board == das1802st_da || + board == das1701st_da || board == das1702st_da) { + dev_dbg(dev->class_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800st-da series\n"); + return das1801st; + break; + case 0x4: + if (board == das1802hr_da || board == das1702hr_da) { + dev_dbg(dev->class_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1802hr-da\n"); + return das1802hr; + break; + case 0x5: + if (board == das1801ao || board == das1802ao || + board == das1701ao || board == das1702ao) { + dev_dbg(dev->class_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800ao series\n"); + return das1801ao; + break; + case 0x6: + if (board == das1802hr || board == das1702hr) { + dev_dbg(dev->class_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1802hr\n"); + return das1802hr; + break; + case 0x7: + if (board == das1801st || board == das1802st || + board == das1701st || board == das1702st) { + dev_dbg(dev->class_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800st series\n"); + return das1801st; + break; + case 0x8: + if (board == das1801hc || board == das1802hc) { + dev_dbg(dev->class_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800hc series\n"); + return das1801hc; + break; + default: + printk + (" Board model: probe returned 0x%x (unknown, please report)\n", + id); + return board; + break; + } + return -1; +} + +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]; + unsigned int dma0 = it->options[2]; + unsigned int dma1 = it->options[3]; + 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 = comedi_board(dev); + 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; + } + } + } + + ret = das1800_init_dma(dev, dma0, dma1); + if (ret < 0) + return ret; + + if (devpriv->ai_buf0 == NULL) { + devpriv->ai_buf0 = + kmalloc(FIFO_SIZE * sizeof(uint16_t), GFP_KERNEL); + if (devpriv->ai_buf0 == NULL) + 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 | SDF_READABLE; + 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; + + if (devpriv) { + if (devpriv->dma0) + free_dma(devpriv->dma0); + if (devpriv->dma1) + free_dma(devpriv->dma1); + kfree(devpriv->ai_buf0); + kfree(devpriv->ai_buf1); + 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 00000000000..d18eea6c01a --- /dev/null +++ b/drivers/staging/comedi/drivers/das6402.c @@ -0,0 +1,547 @@ +/* + * 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) + * (Keithley Metrabyte) 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 "8253.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 count; + unsigned int divider1; + unsigned int divider2; + + unsigned int ao_range; + unsigned int ao_readback[2]; +}; + +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 void das6402_enable_counter(struct comedi_device *dev, bool load) +{ + struct das6402_private *devpriv = dev->private; + unsigned long timer_iobase = dev->iobase + DAS6402_TIMER_BASE; + + if (load) { + i8254_set_mode(timer_iobase, 0, 0, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_iobase, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_iobase, 0, 2, I8254_MODE2 | I8254_BINARY); + + i8254_write(timer_iobase, 0, 0, devpriv->count); + i8254_write(timer_iobase, 0, 1, devpriv->divider1); + i8254_write(timer_iobase, 0, 2, devpriv->divider2); + + } else { + i8254_set_mode(timer_iobase, 0, 0, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_iobase, 0, 1, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_iobase, 0, 2, I8254_MODE0 | I8254_BINARY); + } +} + +static irqreturn_t das6402_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + + das6402_clear_all_interrupts(dev); + + return IRQ_HANDLED; +} + +static int das6402_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + return -EINVAL; +} + +static int das6402_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + return -EINVAL; +} + +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 range = CR_RANGE(insn->chanspec); + unsigned int aref = CR_AREF(insn->chanspec); + unsigned int val; + int ret; + int i; + + val = DAS6402_MODE_RANGE(range) | DAS6402_MODE_POLLED; + if (aref == AREF_DIFF) { + if (chan > s->n_chan / 2) + return -EINVAL; + } else { + val |= DAS6402_MODE_SE; + } + if (comedi_range_is_unipolar(s, range)) + val |= DAS6402_MODE_UNI; + + /* enable software conversion trigger */ + outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG); + + das6402_set_mode(dev, val); + + /* 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; + + val = inw(dev->iobase + DAS6402_AI_DATA_REG); + + if (s->maxdata == 0x0fff) + val >>= 4; + + data[i] = val; + } + + 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]; + + devpriv->ao_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) +{ + struct das6402_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + /* + * If XFER mode is enabled, reading any DAC register + * will update both DAC's simultaneously. + */ + inw(dev->iobase + DAS6402_AO_LSB_REG(chan)); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return insn->n; +} + +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)); + + das6402_enable_counter(dev, false); + + /* 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 = comedi_board(dev); + 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; + } + } + } + + 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_WRITEABLE; + 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; + + /* 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_WRITEABLE; + 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 00000000000..6f7f8d531dd --- /dev/null +++ b/drivers/staging/comedi/drivers/das800.c @@ -0,0 +1,771 @@ +/* + 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 "../comedidev.h" + +#include <linux/delay.h> + +#include "8253.h" +#include "comedi_fc.h" + +#define DAS800_SIZE 8 +#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 count; /* number of data points left to be taken */ + unsigned int divisor1; /* counter 1 value for timed conversions */ + unsigned int divisor2; /* counter 2 value for timed conversions */ + 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 = comedi_board(dev); + 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 void das800_set_frequency(struct comedi_device *dev) +{ + struct das800_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + DAS800_8254; + + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + i8254_write(timer_base, 0, 1, devpriv->divisor1); + i8254_write(timer_base, 0, 2, devpriv->divisor2); +} + +static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct das800_private *devpriv = dev->private; + + devpriv->count = 0; + 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 = comedi_board(dev); + struct das800_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + thisboard->ai_speed); + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_1MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_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 = comedi_board(dev); + struct das800_private *devpriv = dev->private; + 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); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + else /* TRIG_NONE */ + devpriv->count = 0; + + /* 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; + /* set conversion frequency */ + das800_set_frequency(dev); + } + + 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 */ + + /* if there are more data points to collect */ + if (cmd->stop_src == TRIG_NONE || devpriv->count > 0) { + /* write data point to buffer */ + cfc_write_to_buffer(s, val & s->maxdata); + devpriv->count--; + } + } + async->events |= COMEDI_CB_BLOCK; + + if (fifo_overflow) { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + return IRQ_HANDLED; + } + + if (cmd->stop_src == TRIG_NONE || devpriv->count > 0) { + /* 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); + async->events |= COMEDI_CB_EOA; + } + cfc_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 = comedi_board(dev); + 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], DAS800_SIZE); + 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 = comedi_board(dev); + 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; + } + + 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 | SDF_READABLE; + 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 00000000000..ad7a5d53b97 --- /dev/null +++ b/drivers/staging/comedi/drivers/dmm32at.c @@ -0,0 +1,806 @@ +/* + comedi/drivers/dmm32at.c + Diamond Systems mm32at code for a 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 mm32at driver. +Devices: +Author: Perry J. Piplani <perry.j.piplani@nasa.gov> +Updated: Fri Jun 4 09:13:24 CDT 2004 +Status: experimental + +This driver is for the Diamond Systems MM-32-AT board +http://www.diamondsystems.com/products/diamondmm32at It is being used +on serveral projects inside NASA, without problems so far. For analog +input commands, TRIG_EXT is not yet supported at all.. + +Configuration Options: + comedi_config /dev/comedi0 dmm32at baseaddr,irq +*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include "../comedidev.h" + +#include "comedi_fc.h" + +/* Board register addresses */ + +#define DMM32AT_MEMSIZE 0x10 + +#define DMM32AT_CONV 0x00 +#define DMM32AT_AILSB 0x00 +#define DMM32AT_AUXDOUT 0x01 +#define DMM32AT_AIMSB 0x01 +#define DMM32AT_AILOW 0x02 +#define DMM32AT_AIHIGH 0x03 + +#define DMM32AT_DACLSB 0x04 +#define DMM32AT_DACSTAT 0x04 +#define DMM32AT_DACMSB 0x05 + +#define DMM32AT_FIFOCNTRL 0x07 +#define DMM32AT_FIFOSTAT 0x07 + +#define DMM32AT_CNTRL 0x08 +#define DMM32AT_AISTAT 0x08 + +#define DMM32AT_INTCLOCK 0x09 + +#define DMM32AT_CNTRDIO 0x0a + +#define DMM32AT_AICONF 0x0b +#define DMM32AT_AIRBACK 0x0b + +#define DMM32AT_CLK1 0x0d +#define DMM32AT_CLK2 0x0e +#define DMM32AT_CLKCT 0x0f + +#define DMM32AT_DIOA 0x0c +#define DMM32AT_DIOB 0x0d +#define DMM32AT_DIOC 0x0e +#define DMM32AT_DIOCONF 0x0f + +/* Board register values. */ + +/* DMM32AT_DACSTAT 0x04 */ +#define DMM32AT_DACBUSY 0x80 + +/* DMM32AT_FIFOCNTRL 0x07 */ +#define DMM32AT_FIFORESET 0x02 +#define DMM32AT_SCANENABLE 0x04 + +/* DMM32AT_CNTRL 0x08 */ +#define DMM32AT_RESET 0x20 +#define DMM32AT_INTRESET 0x08 +#define DMM32AT_CLKACC 0x00 +#define DMM32AT_DIOACC 0x01 + +/* DMM32AT_AISTAT 0x08 */ +#define DMM32AT_STATUS 0x80 + +/* DMM32AT_INTCLOCK 0x09 */ +#define DMM32AT_ADINT 0x80 +#define DMM32AT_CLKSEL 0x03 + +/* DMM32AT_CNTRDIO 0x0a */ +#define DMM32AT_FREQ12 0x80 + +/* DMM32AT_AICONF 0x0b */ +#define DMM32AT_RANGE_U10 0x0c +#define DMM32AT_RANGE_U5 0x0d +#define DMM32AT_RANGE_B10 0x08 +#define DMM32AT_RANGE_B5 0x00 +#define DMM32AT_SCINT_20 0x00 +#define DMM32AT_SCINT_15 0x10 +#define DMM32AT_SCINT_10 0x20 +#define DMM32AT_SCINT_5 0x30 + +/* 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 */ + +/* DMM32AT_DIOCONF 0x0f */ +#define DMM32AT_DIENABLE 0x80 +#define DMM32AT_DIRA 0x10 +#define DMM32AT_DIRB 0x02 +#define DMM32AT_DIRCL 0x01 +#define DMM32AT_DIRCH 0x08 + +/* 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) + } +}; + +struct dmm32at_private { + + int data; + int ai_inuse; + unsigned int ai_scans_left; + + /* Used for AO readback */ + unsigned int ao_readback[4]; + unsigned char dio_config; + +}; + +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_STATUS) == 0) + return 0; + return -EBUSY; +} + +static int dmm32at_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + unsigned int d; + unsigned short msb, lsb; + unsigned char chan; + int range; + int ret; + + /* get the channel and range number */ + + chan = CR_CHAN(insn->chanspec) & (s->n_chan - 1); + range = CR_RANGE(insn->chanspec); + + /* zero scan and fifo control and reset fifo */ + outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL); + + /* write the ai channel range regs */ + outb(chan, dev->iobase + DMM32AT_AILOW); + outb(chan, dev->iobase + DMM32AT_AIHIGH); + /* set the range bits */ + outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF); + + /* wait for circuit to settle */ + ret = comedi_timeout(dev, s, insn, dmm32at_ai_status, DMM32AT_AIRBACK); + if (ret) + return ret; + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outb(0xff, dev->iobase + DMM32AT_CONV); + + /* wait for conversion to end */ + ret = comedi_timeout(dev, s, insn, dmm32at_ai_status, + DMM32AT_AISTAT); + if (ret) + return ret; + + /* read data */ + lsb = inb(dev->iobase + DMM32AT_AILSB); + msb = inb(dev->iobase + DMM32AT_AIMSB); + + /* invert sign bit to make range unsigned, this is an + idiosyncrasy of the diamond board, it return + conversions as a signed value, i.e. -32768 to + 32767, flipping the bit and interpreting it as + signed gives you a range of 0 to 65535 which is + used by comedi */ + d = ((msb ^ 0x0080) << 8) + lsb; + + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +static int dmm32at_ns_to_timer(unsigned int *ns, int round) +{ + /* trivial timer */ + return *ns; +} + +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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER /*| TRIG_EXT */); + err |= cfc_check_trigger_src(&cmd->convert_src, + TRIG_TIMER /*| TRIG_EXT */); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SCAN_SPEED 1000000 /* in nanoseconds */ +#define MIN_SCAN_SPEED 1000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + MAX_SCAN_SPEED); + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, + MIN_SCAN_SPEED); + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); + } + + if (cmd->convert_src == TRIG_TIMER) { + 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; + } else { + /* external trigger */ + /* see above */ + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, 9); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0xfffffff0); + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + } else { + /* TRIG_NONE */ + err |= cfc_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; + dmm32at_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + dmm32at_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= cfc_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 the counter frequency to 10mhz */ + outb(0, dev->iobase + DMM32AT_CNTRDIO); + + /* get access to the clock regs */ + outb(DMM32AT_CLKACC, dev->iobase + DMM32AT_CNTRL); + + /* 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_ADINT | DMM32AT_CLKSEL, dev->iobase + DMM32AT_INTCLOCK); +} + +static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct dmm32at_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int range; + unsigned char chanlo, chanhi; + int ret; + + if (!cmd->chanlist) + return -EINVAL; + + /* get the channel list and range */ + chanlo = CR_CHAN(cmd->chanlist[0]) & (s->n_chan - 1); + chanhi = chanlo + cmd->chanlist_len - 1; + if (chanhi >= s->n_chan) + return -EINVAL; + range = CR_RANGE(cmd->chanlist[0]); + + /* reset fifo */ + outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL); + + /* set scan enable */ + outb(DMM32AT_SCANENABLE, dev->iobase + DMM32AT_FIFOCNTRL); + + /* write the ai channel range regs */ + outb(chanlo, dev->iobase + DMM32AT_AILOW); + outb(chanhi, dev->iobase + DMM32AT_AIHIGH); + + /* set the range bits */ + outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF); + + /* reset the interrupt just in case */ + outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ai_scans_left = cmd->stop_arg; + else { /* TRIG_NONE */ + devpriv->ai_scans_left = 0xffffffff; /* indicates TRIG_NONE to + * isr */ + } + + /* + * 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_AIRBACK); + if (ret) + return ret; + + if (devpriv->ai_scans_left > 1) { + /* start the clock and enable the interrupts */ + dmm32at_setaitimer(dev, cmd->scan_begin_arg); + } else { + /* start the interrups and initiate a single scan */ + outb(DMM32AT_ADINT, dev->iobase + DMM32AT_INTCLOCK); + outb(0xff, dev->iobase + DMM32AT_CONV); + } + + return 0; + +} + +static int dmm32at_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dmm32at_private *devpriv = dev->private; + + devpriv->ai_scans_left = 1; + return 0; +} + +static irqreturn_t dmm32at_isr(int irq, void *d) +{ + struct comedi_device *dev = d; + struct dmm32at_private *devpriv = dev->private; + unsigned char intstat; + unsigned int samp; + unsigned short msb, lsb; + int i; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + intstat = inb(dev->iobase + DMM32AT_INTCLOCK); + + if (intstat & DMM32AT_ADINT) { + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + + for (i = 0; i < cmd->chanlist_len; i++) { + /* read data */ + lsb = inb(dev->iobase + DMM32AT_AILSB); + msb = inb(dev->iobase + DMM32AT_AIMSB); + + /* invert sign bit to make range unsigned */ + samp = ((msb ^ 0x0080) << 8) + lsb; + comedi_buf_put(s, samp); + } + + if (devpriv->ai_scans_left != 0xffffffff) { /* TRIG_COUNT */ + devpriv->ai_scans_left--; + if (devpriv->ai_scans_left == 0) { + /* disable further interrupts and clocks */ + outb(0x0, dev->iobase + DMM32AT_INTCLOCK); + /* set the buffer to be flushed with an EOF */ + s->async->events |= COMEDI_CB_EOA; + } + + } + /* flush the buffer */ + comedi_event(dev, s); + } + + /* reset the interrupt */ + outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL); + 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_DACSTAT); + if ((status & DMM32AT_DACBUSY) == 0) + return 0; + return -EBUSY; +} + +static int dmm32at_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dmm32at_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned char hi, lo, status; + int ret; + + /* 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++) { + + devpriv->ao_readback[chan] = data[i]; + + /* get the low byte */ + lo = data[i] & 0x00ff; + /* high byte also contains channel number */ + hi = (data[i] >> 8) + chan * (1 << 6); + /* write the low and high values to the board */ + outb(lo, dev->iobase + DMM32AT_DACLSB); + outb(hi, dev->iobase + DMM32AT_DACMSB); + + /* wait for circuit to settle */ + ret = comedi_timeout(dev, s, insn, dmm32at_ao_eoc, 0); + if (ret) + return ret; + + /* dummy read to update trigger the output */ + status = inb(dev->iobase + DMM32AT_DACMSB); + + } + + /* return the number of samples read/written */ + return i; +} + +static int dmm32at_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dmm32at_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 dmm32at_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dmm32at_private *devpriv = dev->private; + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + /* get access to the DIO regs */ + outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); + + /* if either part of dio is set for output */ + if (((devpriv->dio_config & DMM32AT_DIRCL) == 0) || + ((devpriv->dio_config & DMM32AT_DIRCH) == 0)) { + val = (s->state & 0x00ff0000) >> 16; + outb(val, dev->iobase + DMM32AT_DIOC); + } + if ((devpriv->dio_config & DMM32AT_DIRB) == 0) { + val = (s->state & 0x0000ff00) >> 8; + outb(val, dev->iobase + DMM32AT_DIOB); + } + if ((devpriv->dio_config & DMM32AT_DIRA) == 0) { + val = (s->state & 0x000000ff); + outb(val, dev->iobase + DMM32AT_DIOA); + } + } + + val = inb(dev->iobase + DMM32AT_DIOA); + val |= inb(dev->iobase + DMM32AT_DIOB) << 8; + val |= inb(dev->iobase + DMM32AT_DIOC) << 16; + s->state = val; + + data[1] = val; + + return insn->n; +} + +static int dmm32at_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dmm32at_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + unsigned char chanbit; + int ret; + + if (chan < 8) { + mask = 0x0000ff; + chanbit = DMM32AT_DIRA; + } else if (chan < 16) { + mask = 0x00ff00; + chanbit = DMM32AT_DIRB; + } else if (chan < 20) { + mask = 0x0f0000; + chanbit = DMM32AT_DIRCL; + } else { + mask = 0xf00000; + chanbit = DMM32AT_DIRCH; + } + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + if (data[0] == INSN_CONFIG_DIO_OUTPUT) + devpriv->dio_config &= ~chanbit; + else + devpriv->dio_config |= chanbit; + /* get access to the DIO regs */ + outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); + /* set the DIO's to the new configuration setting */ + outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF); + + return insn->n; +} + +static int dmm32at_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct dmm32at_private *devpriv; + int ret; + struct comedi_subdevice *s; + unsigned char aihi, ailo, fifostat, aistat, intstat, airback; + + ret = comedi_request_region(dev, it->options[0], DMM32AT_MEMSIZE); + if (ret) + return ret; + + /* the following just makes sure the board is there and gets + it to a known state */ + + /* reset the board */ + outb(DMM32AT_RESET, dev->iobase + DMM32AT_CNTRL); + + /* allow a millisecond to reset */ + udelay(1000); + + /* zero scan and fifo control */ + outb(0x0, dev->iobase + DMM32AT_FIFOCNTRL); + + /* zero interrupt and clock control */ + outb(0x0, dev->iobase + DMM32AT_INTCLOCK); + + /* write a test channel range, the high 3 bits should drop */ + outb(0x80, dev->iobase + DMM32AT_AILOW); + outb(0xff, dev->iobase + DMM32AT_AIHIGH); + + /* set the range at 10v unipolar */ + outb(DMM32AT_RANGE_U10, dev->iobase + DMM32AT_AICONF); + + /* should take 10 us to settle, here's a hundred */ + udelay(100); + + /* read back the values */ + ailo = inb(dev->iobase + DMM32AT_AILOW); + aihi = inb(dev->iobase + DMM32AT_AIHIGH); + fifostat = inb(dev->iobase + DMM32AT_FIFOSTAT); + aistat = inb(dev->iobase + DMM32AT_AISTAT); + intstat = inb(dev->iobase + DMM32AT_INTCLOCK); + airback = inb(dev->iobase + DMM32AT_AIRBACK); + + if ((ailo != 0x00) || (aihi != 0x1f) || (fifostat != 0x80) || + (aistat != 0x60 || (intstat != 0x00) || airback != 0x0c)) { + dev_err(dev->class_dev, "board detection failed\n"); + return -EIO; + } + + 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]; + } + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + /* we support single-ended (ground) and differential */ + 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_rinsn; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 32; + s->do_cmd = dmm32at_ai_cmd; + s->do_cmdtest = dmm32at_ai_cmdtest; + s->cancel = dmm32at_ai_cancel; + } + + s = &dev->subdevices[1]; + /* analog output subdevice */ + 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_winsn; + s->insn_read = dmm32at_ao_rinsn; + + s = &dev->subdevices[2]; + /* digital i/o subdevice */ + + /* get access to the DIO regs */ + outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); + /* set the DIO's to the defualt input setting */ + devpriv->dio_config = DMM32AT_DIRA | DMM32AT_DIRB | + DMM32AT_DIRCL | DMM32AT_DIRCH | DMM32AT_DIENABLE; + outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF); + + /* set up the subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->state = 0; + s->range_table = &range_digital; + s->insn_bits = dmm32at_dio_insn_bits; + s->insn_config = dmm32at_dio_insn_config; + + 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 low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt2801.c b/drivers/staging/comedi/drivers/dt2801.c new file mode 100644 index 00000000000..4263014426f --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2801.c @@ -0,0 +1,654 @@ +/* + * 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) + +/* Ports */ +#define DT2801_IOSIZE 2 + +/* 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]; + unsigned int ao_readback[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 int 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); + + return 0; +} + +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++) { + stat = 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++) { + stat = 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_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2801_private *devpriv = dev->private; + + data[0] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int dt2801_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2801_private *devpriv = dev->private; + + dt2801_writecmd(dev, DT_C_WRITE_DAIM); + dt2801_writedata(dev, CR_CHAN(insn->chanspec)); + dt2801_writedata2(dev, data[0]); + + devpriv->ao_readback[CR_CHAN(insn->chanspec)] = 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], DT2801_IOSIZE); + 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 = comedi_board(dev); + + 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_read = dt2801_ao_insn_read; + s->insn_write = dt2801_ao_insn_write; + + 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 00000000000..ba7c2ba618e --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2811.c @@ -0,0 +1,490 @@ +/* + 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_SIZE 8 + +#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]; + unsigned int ao_readback[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(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2811_private *devpriv = dev->private; + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + outb(data[i] & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan); + outb((data[i] >> 8) & 0xff, + dev->iobase + DT2811_DADAT0HI + 2 * chan); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int dt2811_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2811_private *devpriv = dev->private; + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +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 = comedi_board(dev); + struct dt2811_private *devpriv; + int ret; + struct comedi_subdevice *s; + + ret = comedi_request_region(dev, it->options[0], DT2811_SIZE); + 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->insn_write = dt2811_ao_insn; + s->insn_read = dt2811_ao_insn_read; + 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 = &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 00000000000..904c9f0e4af --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2814.c @@ -0,0 +1,304 @@ +/* + 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> + +#include "comedi_fc.h" + +#define DT2814_SIZE 2 + +#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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000); + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + DT2814_MAX_SPEED); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 2); + else /* TRIG_NONE */ + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_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 & TRIG_ROUND_MASK); + + 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) { + comedi_error(dev, "spurious interrupt"); + 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_event(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], DT2814_SIZE); + 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 00000000000..b9ac4ed8bab --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2815.c @@ -0,0 +1,225 @@ +/* + 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_SIZE 2 + +#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], DT2815_SIZE); + 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 00000000000..bf589936e54 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2817.c @@ -0,0 +1,151 @@ +/* + 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_SIZE 5 + +#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], DT2817_SIZE); + 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 00000000000..c2a66dcf99f --- /dev/null +++ b/drivers/staging/comedi/drivers/dt282x.c @@ -0,0 +1,1384 @@ +/* + comedi/drivers/dt282x.c + Hardware 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-f), DT2821-G-8DI (dt2821-g), + DT2823 (dt2823), + DT2824-PGH (dt2824-pgh), DT2824-PGL (dt2824-pgl), DT2825 (dt2825), + DT2827 (dt2827), DT2828 (dt2828), 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 + [2] - DMA 1 + [3] - DMA 2 + [4] - AI jumpered for 0=single ended, 1=differential + [5] - AI jumpered for 0=straight binary, 1=2's complement + [6] - AO 0 jumpered for 0=straight binary, 1=2's complement + [7] - AO 1 jumpered for 0=straight binary, 1=2's complement + [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5] + [9] - AO 0 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5], + 4=[-2.5,2.5] + [10]- A0 1 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5], + 4=[-2.5,2.5] + +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. +*/ + +#include <linux/module.h> +#include "../comedidev.h" + +#include <linux/delay.h> +#include <linux/gfp.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <asm/dma.h> + +#include "comedi_fc.h" + +#define DT2821_SIZE 0x10 + +/* + * Registers in the DT282x + */ + +#define DT2821_ADCSR 0x00 /* A/D Control/Status */ +#define DT2821_CHANCSR 0x02 /* Channel Control/Status */ +#define DT2821_ADDAT 0x04 /* A/D data */ +#define DT2821_DACSR 0x06 /* D/A Control/Status */ +#define DT2821_DADAT 0x08 /* D/A data */ +#define DT2821_DIODAT 0x0a /* digital data */ +#define DT2821_SUPCSR 0x0c /* Supervisor Control/Status */ +#define DT2821_TMRCTR 0x0e /* Timer/Counter */ + +/* + * At power up, some registers are in a well-known state. The + * masks and values are as follows: + */ + +#define DT2821_ADCSR_MASK 0xfff0 +#define DT2821_ADCSR_VAL 0x7c00 + +#define DT2821_CHANCSR_MASK 0xf0f0 +#define DT2821_CHANCSR_VAL 0x70f0 + +#define DT2821_DACSR_MASK 0x7c93 +#define DT2821_DACSR_VAL 0x7c90 + +#define DT2821_SUPCSR_MASK 0xf8ff +#define DT2821_SUPCSR_VAL 0x0000 + +#define DT2821_TMRCTR_MASK 0xff00 +#define DT2821_TMRCTR_VAL 0xf000 + +/* + * Bit fields of each register + */ + +/* ADCSR */ + +#define DT2821_ADERR 0x8000 /* (R) 1 for A/D error */ +#define DT2821_ADCLK 0x0200 /* (R/W) A/D clock enable */ + /* 0x7c00 read as 1's */ +#define DT2821_MUXBUSY 0x0100 /* (R) multiplexer busy */ +#define DT2821_ADDONE 0x0080 /* (R) A/D done */ +#define DT2821_IADDONE 0x0040 /* (R/W) interrupt on A/D done */ + /* 0x0030 gain select */ + /* 0x000f channel select */ + +/* CHANCSR */ + +#define DT2821_LLE 0x8000 /* (R/W) Load List Enable */ + /* 0x7000 read as 1's */ + /* 0x0f00 (R) present address */ + /* 0x00f0 read as 1's */ + /* 0x000f (R) number of entries - 1 */ + +/* DACSR */ + +#define DT2821_DAERR 0x8000 /* (R) D/A error */ +#define DT2821_YSEL 0x0200 /* (R/W) DAC 1 select */ +#define DT2821_SSEL 0x0100 /* (R/W) single channel select */ +#define DT2821_DACRDY 0x0080 /* (R) DAC ready */ +#define DT2821_IDARDY 0x0040 /* (R/W) interrupt on DAC ready */ +#define DT2821_DACLK 0x0020 /* (R/W) D/A clock enable */ +#define DT2821_HBOE 0x0002 /* (R/W) DIO high byte output enable */ +#define DT2821_LBOE 0x0001 /* (R/W) DIO low byte output enable */ + +/* SUPCSR */ + +#define DT2821_DMAD 0x8000 /* (R) DMA done */ +#define DT2821_ERRINTEN 0x4000 /* (R/W) interrupt on error */ +#define DT2821_CLRDMADNE 0x2000 /* (W) clear DMA done */ +#define DT2821_DDMA 0x1000 /* (R/W) dual DMA */ +#define DT2821_DS1 0x0800 /* (R/W) DMA select 1 */ +#define DT2821_DS0 0x0400 /* (R/W) DMA select 0 */ +#define DT2821_BUFFB 0x0200 /* (R/W) buffer B selected */ +#define DT2821_SCDN 0x0100 /* (R) scan done */ +#define DT2821_DACON 0x0080 /* (W) DAC single conversion */ +#define DT2821_ADCINIT 0x0040 /* (W) A/D initialize */ +#define DT2821_DACINIT 0x0020 /* (W) D/A initialize */ +#define DT2821_PRLD 0x0010 /* (W) preload multiplexer */ +#define DT2821_STRIG 0x0008 /* (W) software trigger */ +#define DT2821_XTRIG 0x0004 /* (R/W) external trigger enable */ +#define DT2821_XCLK 0x0002 /* (R/W) external clock enable */ +#define DT2821_BDINIT 0x0001 /* (W) initialize board */ + +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) + } +}; + +struct dt282x_board { + const char *name; + int adbits; + int adchan_se; + int adchan_di; + int ai_speed; + int ispgl; + int dachan; + int dabits; +}; + +struct dt282x_private { + int ad_2scomp; /* we have 2's comp jumper set */ + int da0_2scomp; /* same, for DAC0 */ + int da1_2scomp; /* same, for DAC1 */ + + const struct comedi_lrange *darangelist[2]; + + unsigned short ao[2]; + + volatile int dacsr; /* software copies of registers */ + volatile int adcsr; + volatile int supcsr; + + volatile int ntrig; + volatile int nread; + + struct { + int chan; + unsigned short *buf; /* DMA buffer */ + volatile int size; /* size of current transfer */ + } dma[2]; + int dma_maxsize; /* max size of DMA transfer (in bytes) */ + int usedma; /* driver uses DMA */ + volatile int current_dma_index; + int dma_dir; +}; + +/* + * Some useless abstractions + */ +#define chan_to_DAC(a) ((a)&1) + +static int prep_ai_dma(struct comedi_device *dev, int chan, int size); +static int prep_ao_dma(struct comedi_device *dev, int chan, int size); +static int dt282x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int dt282x_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int dt282x_ns_to_timer(int *nanosec, int round_mode); +static void dt282x_disable_dma(struct comedi_device *dev); + +static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2); + +static void dt282x_munge(struct comedi_device *dev, unsigned short *buf, + unsigned int nbytes) +{ + const struct dt282x_board *board = comedi_board(dev); + struct dt282x_private *devpriv = dev->private; + unsigned int i; + unsigned short mask = (1 << board->adbits) - 1; + unsigned short sign = 1 << (board->adbits - 1); + int n; + + if (devpriv->ad_2scomp) + sign = 1 << (board->adbits - 1); + else + sign = 0; + + if (nbytes % 2) + comedi_error(dev, "bug! odd number of bytes from dma xfer"); + n = nbytes / 2; + for (i = 0; i < n; i++) + buf[i] = (buf[i] & mask) ^ sign; +} + +static void dt282x_ao_dma_interrupt(struct comedi_device *dev) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->write_subdev; + void *ptr; + int size; + int i; + + outw(devpriv->supcsr | DT2821_CLRDMADNE, dev->iobase + DT2821_SUPCSR); + + if (!s->async->prealloc_buf) { + dev_err(dev->class_dev, "no buffer in %s\n", __func__); + return; + } + + i = devpriv->current_dma_index; + ptr = devpriv->dma[i].buf; + + disable_dma(devpriv->dma[i].chan); + + devpriv->current_dma_index = 1 - i; + + size = cfc_read_array_from_buffer(s, ptr, devpriv->dma_maxsize); + if (size == 0) { + dev_err(dev->class_dev, "AO underrun\n"); + s->async->events |= COMEDI_CB_OVERFLOW; + return; + } + prep_ao_dma(dev, i, size); + return; +} + +static void dt282x_ai_dma_interrupt(struct comedi_device *dev) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + void *ptr; + int size; + int i; + int ret; + + outw(devpriv->supcsr | DT2821_CLRDMADNE, dev->iobase + DT2821_SUPCSR); + + if (!s->async->prealloc_buf) { + dev_err(dev->class_dev, "no buffer in %s\n", __func__); + return; + } + + i = devpriv->current_dma_index; + ptr = devpriv->dma[i].buf; + size = devpriv->dma[i].size; + + disable_dma(devpriv->dma[i].chan); + + devpriv->current_dma_index = 1 - i; + + dt282x_munge(dev, ptr, size); + ret = cfc_write_array_to_buffer(s, ptr, size); + if (ret != size) { + s->async->events |= COMEDI_CB_OVERFLOW; + return; + } + devpriv->nread -= size / 2; + + 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_DDMA); + outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR); + } +#endif + /* restart the channel */ + prep_ai_dma(dev, i, 0); +} + +static int prep_ai_dma(struct comedi_device *dev, int dma_index, int n) +{ + struct dt282x_private *devpriv = dev->private; + int dma_chan; + unsigned long dma_ptr; + unsigned long flags; + + if (!devpriv->ntrig) + return 0; + + if (n == 0) + n = devpriv->dma_maxsize; + if (n > devpriv->ntrig * 2) + n = devpriv->ntrig * 2; + devpriv->ntrig -= n / 2; + + devpriv->dma[dma_index].size = n; + dma_chan = devpriv->dma[dma_index].chan; + dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf); + + set_dma_mode(dma_chan, DMA_MODE_READ); + flags = claim_dma_lock(); + clear_dma_ff(dma_chan); + set_dma_addr(dma_chan, dma_ptr); + set_dma_count(dma_chan, n); + release_dma_lock(flags); + + enable_dma(dma_chan); + + return n; +} + +static int prep_ao_dma(struct comedi_device *dev, int dma_index, int n) +{ + struct dt282x_private *devpriv = dev->private; + int dma_chan; + unsigned long dma_ptr; + unsigned long flags; + + devpriv->dma[dma_index].size = n; + dma_chan = devpriv->dma[dma_index].chan; + dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf); + + set_dma_mode(dma_chan, DMA_MODE_WRITE); + flags = claim_dma_lock(); + clear_dma_ff(dma_chan); + set_dma_addr(dma_chan, dma_ptr); + set_dma_count(dma_chan, n); + release_dma_lock(flags); + + enable_dma(dma_chan); + + return n; +} + +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) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + adcsr = inw(dev->iobase + DT2821_ADCSR); + dacsr = inw(dev->iobase + DT2821_DACSR); + supcsr = inw(dev->iobase + DT2821_SUPCSR); + if (supcsr & DT2821_DMAD) { + if (devpriv->dma_dir == DMA_MODE_READ) + dt282x_ai_dma_interrupt(dev); + else + dt282x_ao_dma_interrupt(dev); + handled = 1; + } + if (adcsr & DT2821_ADERR) { + if (devpriv->nread != 0) { + comedi_error(dev, "A/D error"); + s->async->events |= COMEDI_CB_ERROR; + } + handled = 1; + } + if (dacsr & DT2821_DAERR) { + comedi_error(dev, "D/A error"); + s_ao->async->events |= COMEDI_CB_ERROR; + handled = 1; + } +#if 0 + if (adcsr & DT2821_ADDONE) { + int ret; + unsigned short data; + + data = inw(dev->iobase + DT2821_ADDAT); + data &= (1 << board->adbits) - 1; + + if (devpriv->ad_2scomp) + data ^= 1 << (board->adbits - 1); + ret = comedi_buf_put(s, data); + + if (ret == 0) + s->async->events |= COMEDI_CB_OVERFLOW; + + devpriv->nread--; + if (!devpriv->nread) { + s->async->events |= COMEDI_CB_EOA; + } else { + if (supcsr & DT2821_SCDN) + outw(devpriv->supcsr | DT2821_STRIG, + dev->iobase + DT2821_SUPCSR); + } + handled = 1; + } +#endif + cfc_handle_events(dev, s); + cfc_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; + unsigned int i; + unsigned int chan, range; + + outw(DT2821_LLE | (n - 1), dev->iobase + DT2821_CHANCSR); + for (i = 0; i < n; i++) { + chan = CR_CHAN(chanlist[i]); + range = CR_RANGE(chanlist[i]); + outw(devpriv->adcsr | (range << 4) | chan, + dev->iobase + DT2821_ADCSR); + } + outw(n - 1, dev->iobase + DT2821_CHANCSR); +} + +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); + switch (context) { + case DT2821_MUXBUSY: + if ((status & DT2821_MUXBUSY) == 0) + return 0; + break; + case DT2821_ADDONE: + if (status & DT2821_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) +{ + const struct dt282x_board *board = comedi_board(dev); + struct dt282x_private *devpriv = dev->private; + int ret; + int i; + + /* XXX should we really be enabling the ad clock here? */ + devpriv->adcsr = DT2821_ADCLK; + outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR); + + dt282x_load_changain(dev, 1, &insn->chanspec); + + outw(devpriv->supcsr | DT2821_PRLD, dev->iobase + DT2821_SUPCSR); + ret = comedi_timeout(dev, s, insn, dt282x_ai_timeout, DT2821_MUXBUSY); + if (ret) + return ret; + + for (i = 0; i < insn->n; i++) { + outw(devpriv->supcsr | DT2821_STRIG, + dev->iobase + DT2821_SUPCSR); + + ret = comedi_timeout(dev, s, insn, dt282x_ai_timeout, + DT2821_ADDONE); + if (ret) + return ret; + + data[i] = + inw(dev->iobase + + DT2821_ADDAT) & ((1 << board->adbits) - 1); + if (devpriv->ad_2scomp) + data[i] ^= (1 << (board->adbits - 1)); + } + + return i; +} + +static int dt282x_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + const struct dt282x_board *board = comedi_board(dev); + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } + + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 4000); + +#define SLOWEST_TIMER (250*(1<<15)*255) + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER); + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, board->ai_speed); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { /* TRIG_NONE */ + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + arg = cmd->convert_arg; + dt282x_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); + err |= cfc_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) +{ + const struct dt282x_board *board = comedi_board(dev); + struct dt282x_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int timer; + int ret; + + if (devpriv->usedma == 0) { + comedi_error(dev, + "driver requires 2 dma channels" + " to execute command"); + return -EIO; + } + + dt282x_disable_dma(dev); + + if (cmd->convert_arg < board->ai_speed) + cmd->convert_arg = board->ai_speed; + timer = dt282x_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_NEAREST); + outw(timer, dev->iobase + DT2821_TMRCTR); + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0; + } else { + /* external trigger */ + devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0 | DT2821_DS1; + } + outw(devpriv->supcsr | DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_ADCINIT, + dev->iobase + DT2821_SUPCSR); + + devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg; + devpriv->nread = devpriv->ntrig; + + devpriv->dma_dir = DMA_MODE_READ; + devpriv->current_dma_index = 0; + prep_ai_dma(dev, 0, 0); + if (devpriv->ntrig) { + prep_ai_dma(dev, 1, 0); + devpriv->supcsr |= DT2821_DDMA; + outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR); + } + + devpriv->adcsr = 0; + + dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist); + + devpriv->adcsr = DT2821_ADCLK | DT2821_IADDONE; + outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR); + + outw(devpriv->supcsr | DT2821_PRLD, dev->iobase + DT2821_SUPCSR); + ret = comedi_timeout(dev, s, NULL, dt282x_ai_timeout, DT2821_MUXBUSY); + if (ret) + return ret; + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + outw(devpriv->supcsr | DT2821_STRIG, + dev->iobase + DT2821_SUPCSR); + } else { + devpriv->supcsr |= DT2821_XTRIG; + outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR); + } + + return 0; +} + +static void dt282x_disable_dma(struct comedi_device *dev) +{ + struct dt282x_private *devpriv = dev->private; + + if (devpriv->usedma) { + disable_dma(devpriv->dma[0].chan); + disable_dma(devpriv->dma[1].chan); + } +} + +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); + + devpriv->supcsr = 0; + outw(devpriv->supcsr | DT2821_ADCINIT, dev->iobase + DT2821_SUPCSR); + + return 0; +} + +static int dt282x_ns_to_timer(int *nanosec, int round_mode) +{ + int prescale, base, divider; + + for (prescale = 0; prescale < 16; prescale++) { + if (prescale == 1) + continue; + base = 250 * (1 << prescale); + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + if (divider < 256) { + *nanosec = divider * base; + return (prescale << 8) | (255 - divider); + } + } + base = 250 * (1 << 15); + divider = 255; + *nanosec = divider * base; + return (15 << 8) | (255 - divider); +} + +/* + * Analog output routine. Selects single channel conversion, + * selects correct channel, converts from 2's compliment to + * offset binary if necessary, loads the data into the DAC + * data register, and performs the conversion. + */ +static int dt282x_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt282x_private *devpriv = dev->private; + + data[0] = devpriv->ao[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int dt282x_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct dt282x_board *board = comedi_board(dev); + struct dt282x_private *devpriv = dev->private; + unsigned short d; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + d = data[0]; + d &= (1 << board->dabits) - 1; + devpriv->ao[chan] = d; + + devpriv->dacsr |= DT2821_SSEL; + + if (chan) { + /* select channel */ + devpriv->dacsr |= DT2821_YSEL; + if (devpriv->da0_2scomp) + d ^= (1 << (board->dabits - 1)); + } else { + devpriv->dacsr &= ~DT2821_YSEL; + if (devpriv->da1_2scomp) + d ^= (1 << (board->dabits - 1)); + } + + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); + + outw(d, dev->iobase + DT2821_DADAT); + + outw(devpriv->supcsr | DT2821_DACON, dev->iobase + DT2821_SUPCSR); + + return 1; +} + +static int dt282x_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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, 5000); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { /* TRIG_NONE */ + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + arg = cmd->scan_begin_arg; + dt282x_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); + err |= cfc_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; + int size; + + if (trig_num != cmd->start_src) + return -EINVAL; + + size = cfc_read_array_from_buffer(s, devpriv->dma[0].buf, + devpriv->dma_maxsize); + if (size == 0) { + dev_err(dev->class_dev, "AO underrun\n"); + return -EPIPE; + } + prep_ao_dma(dev, 0, size); + + size = cfc_read_array_from_buffer(s, devpriv->dma[1].buf, + devpriv->dma_maxsize); + if (size == 0) { + dev_err(dev->class_dev, "AO underrun\n"); + return -EPIPE; + } + prep_ao_dma(dev, 1, size); + + outw(devpriv->supcsr | DT2821_STRIG, dev->iobase + DT2821_SUPCSR); + 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; + int timer; + struct comedi_cmd *cmd = &s->async->cmd; + + if (devpriv->usedma == 0) { + comedi_error(dev, + "driver requires 2 dma channels" + " to execute command"); + return -EIO; + } + + dt282x_disable_dma(dev); + + devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS1 | DT2821_DDMA; + outw(devpriv->supcsr | DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_DACINIT, + dev->iobase + DT2821_SUPCSR); + + devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len; + devpriv->nread = devpriv->ntrig; + + devpriv->dma_dir = DMA_MODE_WRITE; + devpriv->current_dma_index = 0; + + timer = dt282x_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_NEAREST); + outw(timer, dev->iobase + DT2821_TMRCTR); + + devpriv->dacsr = DT2821_SSEL | DT2821_DACLK | DT2821_IDARDY; + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); + + 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); + + devpriv->dacsr = 0; + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); + + devpriv->supcsr = 0; + outw(devpriv->supcsr | DT2821_DACINIT, dev->iobase + DT2821_SUPCSR); + + 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); + + data[1] = inw(dev->iobase + DT2821_DIODAT); + + 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_LBOE | DT2821_HBOE); + if (s->io_bits & 0x00ff) + devpriv->dacsr |= DT2821_LBOE; + if (s->io_bits & 0xff00) + devpriv->dacsr |= DT2821_HBOE; + + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); + + 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]; + } else { + if (x < 0 || x >= 4) + x = 0; + return ai_range_table[x]; + } +} + +static const struct comedi_lrange *const ao_range_table[] = { + &range_bipolar10, + &range_unipolar10, + &range_bipolar5, + &range_unipolar5, + &range_bipolar2_5 +}; + +static const struct comedi_lrange *opt_ao_range_lkup(int x) +{ + if (x < 0 || x >= 5) + x = 0; + return ao_range_table[x]; +} + +enum { /* i/o base, irq, dma channels */ + opt_iobase = 0, opt_irq, opt_dma1, opt_dma2, + opt_diff, /* differential */ + opt_ai_twos, opt_ao0_twos, opt_ao1_twos, /* twos comp */ + opt_ai_range, opt_ao0_range, opt_ao1_range, /* range */ +}; + +static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2) +{ + struct dt282x_private *devpriv = dev->private; + int ret; + + devpriv->usedma = 0; + + if (!dma1 && !dma2) + return 0; + + if (dma1 == dma2 || dma1 < 5 || dma2 < 5 || dma1 > 7 || dma2 > 7) + return -EINVAL; + + if (dma2 < dma1) { + int i; + i = dma1; + dma1 = dma2; + dma2 = i; + } + + ret = request_dma(dma1, "dt282x A"); + if (ret) + return -EBUSY; + devpriv->dma[0].chan = dma1; + + ret = request_dma(dma2, "dt282x B"); + if (ret) + return -EBUSY; + devpriv->dma[1].chan = dma2; + + devpriv->dma_maxsize = PAGE_SIZE; + devpriv->dma[0].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); + devpriv->dma[1].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); + if (!devpriv->dma[0].buf || !devpriv->dma[1].buf) + return -ENOMEM; + + devpriv->usedma = 1; + + 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 = comedi_board(dev); + struct dt282x_private *devpriv; + struct comedi_subdevice *s; + int ret; + int i; + + ret = comedi_request_region(dev, it->options[0], DT2821_SIZE); + if (ret) + return ret; + + outw(DT2821_BDINIT, dev->iobase + DT2821_SUPCSR); + i = inw(dev->iobase + DT2821_ADCSR); + + if (((inw(dev->iobase + DT2821_ADCSR) & DT2821_ADCSR_MASK) + != DT2821_ADCSR_VAL) || + ((inw(dev->iobase + DT2821_CHANCSR) & DT2821_CHANCSR_MASK) + != DT2821_CHANCSR_VAL) || + ((inw(dev->iobase + DT2821_DACSR) & DT2821_DACSR_MASK) + != DT2821_DACSR_VAL) || + ((inw(dev->iobase + DT2821_SUPCSR) & DT2821_SUPCSR_MASK) + != DT2821_SUPCSR_VAL) || + ((inw(dev->iobase + DT2821_TMRCTR) & DT2821_TMRCTR_MASK) + != DT2821_TMRCTR_VAL)) { + dev_err(dev->class_dev, "board not found\n"); + return -EIO; + } + /* should do board test */ + + if (it->options[opt_irq] > 0) { + ret = request_irq(it->options[opt_irq], dt282x_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[opt_irq]; + } + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + if (dev->irq) { + ret = dt282x_grab_dma(dev, it->options[opt_dma1], + it->options[opt_dma2]); + if (ret < 0) + return ret; + } + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + s = &dev->subdevices[0]; + + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | + ((it->options[opt_diff]) ? SDF_DIFF : SDF_COMMON); + s->n_chan = + (it->options[opt_diff]) ? board->adchan_di : board->adchan_se; + s->insn_read = dt282x_ai_insn_read; + s->maxdata = (1 << board->adbits) - 1; + s->range_table = + opt_ai_range_lkup(board->ispgl, it->options[opt_ai_range]); + devpriv->ad_2scomp = it->options[opt_ai_twos]; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 16; + s->do_cmdtest = dt282x_ai_cmdtest; + s->do_cmd = dt282x_ai_cmd; + s->cancel = dt282x_ai_cancel; + } + + s = &dev->subdevices[1]; + + s->n_chan = board->dachan; + if (s->n_chan) { + /* ao subsystem */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->insn_read = dt282x_ao_insn_read; + s->insn_write = dt282x_ao_insn_write; + s->maxdata = (1 << board->dabits) - 1; + s->range_table_list = devpriv->darangelist; + devpriv->darangelist[0] = + opt_ao_range_lkup(it->options[opt_ao0_range]); + devpriv->darangelist[1] = + opt_ao_range_lkup(it->options[opt_ao1_range]); + devpriv->da0_2scomp = it->options[opt_ao0_twos]; + devpriv->da1_2scomp = it->options[opt_ao1_twos]; + if (dev->irq) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->len_chanlist = 2; + s->do_cmdtest = dt282x_ao_cmdtest; + s->do_cmd = dt282x_ao_cmd; + s->cancel = dt282x_ao_cancel; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + /* dio subsystem */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->insn_bits = dt282x_dio_insn_bits; + s->insn_config = dt282x_dio_insn_config; + s->maxdata = 1; + s->range_table = &range_digital; + + return 0; +} + +static void dt282x_detach(struct comedi_device *dev) +{ + struct dt282x_private *devpriv = dev->private; + + if (dev->private) { + if (devpriv->dma[0].chan) + free_dma(devpriv->dma[0].chan); + if (devpriv->dma[1].chan) + free_dma(devpriv->dma[1].chan); + if (devpriv->dma[0].buf) + free_page((unsigned long)devpriv->dma[0].buf); + if (devpriv->dma[1].buf) + free_page((unsigned long)devpriv->dma[1].buf); + } + comedi_legacy_detach(dev); +} + +static const struct dt282x_board boardtypes[] = { + { + .name = "dt2821", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, { + .name = "dt2821-f", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 6500, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, { + .name = "dt2821-g", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 4000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, { + .name = "dt2823", + .adbits = 16, + .adchan_se = 0, + .adchan_di = 4, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 16, + }, { + .name = "dt2824-pgh", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 0, + .dachan = 0, + .dabits = 0, + }, { + .name = "dt2824-pgl", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 1, + .dachan = 0, + .dabits = 0, + }, { + .name = "dt2825", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 1, + .dachan = 2, + .dabits = 12, + }, { + .name = "dt2827", + .adbits = 16, + .adchan_se = 0, + .adchan_di = 4, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, { + .name = "dt2828", + .adbits = 12, + .adchan_se = 4, + .adchan_di = 0, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, { + .name = "dt2829", + .adbits = 16, + .adchan_se = 8, + .adchan_di = 0, + .ai_speed = 33250, + .ispgl = 0, + .dachan = 2, + .dabits = 16, + }, { + .name = "dt21-ez", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, { + .name = "dt23-ez", + .adbits = 16, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 0, + .dabits = 0, + }, { + .name = "dt24-ez", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 0, + .dabits = 0, + }, { + .name = "dt24-ez-pgl", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 1, + .dachan = 0, + .dabits = 0, + }, +}; + +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 low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt3000.c b/drivers/staging/comedi/drivers/dt3000.c new file mode 100644 index 00000000000..4ab4de00592 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt3000.c @@ -0,0 +1,814 @@ +/* + 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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.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 { + void __iomem *io_addr; + unsigned int lock; + unsigned int ao_readback[2]; + unsigned int ai_front; + unsigned int ai_rear; +}; + +#define TIMEOUT 100 + +static void dt3k_send_cmd(struct comedi_device *dev, unsigned int cmd) +{ + struct dt3k_private *devpriv = dev->private; + int i; + unsigned int status = 0; + + writew(cmd, devpriv->io_addr + DPR_Command_Mbx); + + for (i = 0; i < TIMEOUT; i++) { + status = readw(devpriv->io_addr + 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) +{ + struct dt3k_private *devpriv = dev->private; + + writew(subsys, devpriv->io_addr + DPR_SubSys); + + writew(chan, devpriv->io_addr + DPR_Params(0)); + writew(gain, devpriv->io_addr + DPR_Params(1)); + + dt3k_send_cmd(dev, CMD_READSINGLE); + + return readw(devpriv->io_addr + DPR_Params(2)); +} + +static void dt3k_writesingle(struct comedi_device *dev, unsigned int subsys, + unsigned int chan, unsigned int data) +{ + struct dt3k_private *devpriv = dev->private; + + writew(subsys, devpriv->io_addr + DPR_SubSys); + + writew(chan, devpriv->io_addr + DPR_Params(0)); + writew(0, devpriv->io_addr + DPR_Params(1)); + writew(data, devpriv->io_addr + 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(devpriv->io_addr + 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(devpriv->io_addr + DPR_ADC_buffer + rear); + comedi_buf_put(s, data); + rear++; + if (rear >= AI_FIFO_DEPTH) + rear = 0; + } + + devpriv->ai_rear = rear; + writew(rear, devpriv->io_addr + DPR_AD_Buf_Rear); +} + +static int dt3k_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct dt3k_private *devpriv = dev->private; + + writew(SUBS_AI, devpriv->io_addr + DPR_SubSys); + dt3k_send_cmd(dev, CMD_STOP); + + writew(0, devpriv->io_addr + 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 dt3k_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + + if (!dev->attached) + return IRQ_NONE; + + status = readw(devpriv->io_addr + DPR_Intr_Flag); + + if (status & DT3000_ADFULL) { + dt3k_ai_empty_fifo(dev, s); + s->async->events |= COMEDI_CB_BLOCK; + } + + if (status & (DT3000_ADSWERR | DT3000_ADHWERR)) + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + + debug_n_ints++; + if (debug_n_ints >= 10) + s->async->events |= COMEDI_CB_EOA; + + cfc_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *nanosec, + unsigned int round_mode) +{ + 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 (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_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 = comedi_board(dev); + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + this_board->ai_speed); + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, + 100 * 16 * 65535); + } + + if (cmd->convert_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + this_board->ai_speed); + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, + 50 * 16 * 65535); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + else /* TRIG_NONE */ + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= cfc_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 dt3k_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int i; + unsigned int chan, range, aref; + unsigned int divider; + unsigned int tscandiv; + unsigned int mode; + + for (i = 0; i < cmd->chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + + writew((range << 6) | chan, + devpriv->io_addr + DPR_ADC_buffer + i); + } + aref = CR_AREF(cmd->chanlist[0]); + + writew(cmd->scan_end_arg, devpriv->io_addr + DPR_Params(0)); + + if (cmd->convert_src == TRIG_TIMER) { + divider = dt3k_ns_to_timer(50, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + writew((divider >> 16), devpriv->io_addr + DPR_Params(1)); + writew((divider & 0xffff), devpriv->io_addr + DPR_Params(2)); + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + tscandiv = dt3k_ns_to_timer(100, &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + writew((tscandiv >> 16), devpriv->io_addr + DPR_Params(3)); + writew((tscandiv & 0xffff), devpriv->io_addr + DPR_Params(4)); + } + + mode = DT3000_AD_RETRIG_INTERNAL | 0 | 0; + writew(mode, devpriv->io_addr + DPR_Params(5)); + writew(aref == AREF_DIFF, devpriv->io_addr + DPR_Params(6)); + + writew(AI_FIFO_DEPTH / 2, devpriv->io_addr + DPR_Params(7)); + + writew(SUBS_AI, devpriv->io_addr + DPR_SubSys); + dt3k_send_cmd(dev, CMD_CONFIG); + + writew(DT3000_ADFULL | DT3000_ADSWERR | DT3000_ADHWERR, + devpriv->io_addr + DPR_Int_Mask); + + debug_n_ints = 0; + + writew(SUBS_AI, devpriv->io_addr + 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(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt3k_private *devpriv = dev->private; + int i; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + for (i = 0; i < insn->n; i++) { + dt3k_writesingle(dev, SUBS_AO, chan, data[i]); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int dt3k_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt3k_private *devpriv = dev->private; + int i; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static void dt3k_dio_config(struct comedi_device *dev, int bits) +{ + struct dt3k_private *devpriv = dev->private; + + /* XXX */ + writew(SUBS_DOUT, devpriv->io_addr + DPR_SubSys); + + writew(bits, devpriv->io_addr + DPR_Params(0)); +#if 0 + /* don't know */ + writew(0, devpriv->io_addr + DPR_Params(1)); + writew(0, devpriv->io_addr + 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) +{ + struct dt3k_private *devpriv = dev->private; + unsigned int addr = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + writew(SUBS_MEM, devpriv->io_addr + DPR_SubSys); + writew(addr, devpriv->io_addr + DPR_Params(0)); + writew(1, devpriv->io_addr + DPR_Params(1)); + + dt3k_send_cmd(dev, CMD_READCODE); + + data[i] = readw(devpriv->io_addr + 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; + + devpriv->io_addr = pci_ioremap_bar(pcidev, 0); + if (!devpriv->io_addr) + 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->insn_read = dt3k_ao_insn_read; + s->insn_write = dt3k_ao_insn; + s->maxdata = (1 << this_board->dabits) - 1; + s->len_chanlist = 1; + s->range_table = &range_bipolar10; + + 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 void dt3000_detach(struct comedi_device *dev) +{ + struct dt3k_private *devpriv = dev->private; + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->io_addr) + iounmap(devpriv->io_addr); + } + comedi_pci_disable(dev); +} + +static struct comedi_driver dt3000_driver = { + .driver_name = "dt3000", + .module = THIS_MODULE, + .auto_attach = dt3000_auto_attach, + .detach = dt3000_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 00000000000..b3aeb6fb2ad --- /dev/null +++ b/drivers/staging/comedi/drivers/dt9812.c @@ -0,0 +1,880 @@ +/* + * 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 <linux/usb.h> + +#include "../comedidev.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; + u16 ao_shadow[2]; +}; + +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); + devpriv->ao_shadow[channel] = value; + + 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; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + down(&devpriv->sem); + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_shadow[chan]; + up(&devpriv->sem); + + return insn->n; +} + +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 ret; + int i; + + for (i = 0; i < insn->n; i++) { + ret = dt9812_analog_out(dev, chan, data[i]); + if (ret) + return ret; + } + + 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; + + 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_WRITEABLE; + 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_WRITEABLE; + 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; + + devpriv->ao_shadow[0] = is_unipolar ? 0x0000 : 0x0800; + devpriv->ao_shadow[1] = 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 00000000000..e5593f8c740 --- /dev/null +++ b/drivers/staging/comedi/drivers/dyna_pci10xx.c @@ -0,0 +1,281 @@ +/* + * 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 + Devices: Dynalog India PCI DAQ Cards, http://www.dynalogindia.com/ + Author: Prashant Shah <pshah.mumbai@gmail.com> + Developed at Automation Labs, Chemical Dept., IIT Bombay, India. + Prof. Kannan Moudgalya <kannan@iitb.ac.in> + http://www.iitb.ac.in + Status: Stable + Version: 1.0 + Device Supported : + - Dynalog PCI 1050 + + 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/pci.h> +#include <linux/mutex.h> + +#include "../comedidev.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 | SDF_GROUND; + 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 | SDF_GROUND; + 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; + + if (devpriv) + mutex_destroy(&devpriv->mutex); + comedi_pci_disable(dev); +} + +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 00000000000..4e410f3b0e2 --- /dev/null +++ b/drivers/staging/comedi/drivers/fl512.c @@ -0,0 +1,178 @@ +/* + * 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)) + +struct fl512_private { + unsigned short ao_readback[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) +{ + struct fl512_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = devpriv->ao_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)); + } + devpriv->ao_readback[chan] = val; + + return insn->n; +} + +static int fl512_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct fl512_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return insn->n; +} + +static int fl512_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct fl512_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; + + 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; + s->insn_read = fl512_ao_insn_read; + + 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 00000000000..22333c1ad88 --- /dev/null +++ b/drivers/staging/comedi/drivers/gsc_hpdi.c @@ -0,0 +1,751 @@ +/* + * 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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "plx9080.h" +#include "comedi_fc.h" + +/* + * PCI BAR2 Register map (devpriv->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; + void __iomem *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; + } + cfc_write_array_to_buffer(s, devpriv->desc_dio_buffer[idx], + size * sizeof(uint32_t)); + 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(devpriv->mmio + INTERRUPT_STATUS_REG); + hpdi_board_status = readl(devpriv->mmio + BOARD_STATUS_REG); + + if (hpdi_intr_status) + writel(hpdi_intr_status, devpriv->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_EOA | COMEDI_CB_ERROR; + } + + if (hpdi_board_status & RX_UNDERRUN_BIT) { + dev_err(dev->class_dev, "rx fifo underrun\n"); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } + + if (devpriv->dio_count == 0) + async->events |= COMEDI_CB_EOA; + + cfc_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) +{ + struct hpdi_private *devpriv = dev->private; + + writel(0, devpriv->mmio + BOARD_CONTROL_REG); + writel(0, devpriv->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, devpriv->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, + devpriv->mmio + BOARD_STATUS_REG); + + /* enable interrupts */ + writel(RX_FULL_INTR, devpriv->mmio + INTERRUPT_CONTROL_REG); + + writel(RX_ENABLE_BIT, devpriv->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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (!cmd->chanlist_len || !cmd->chanlist) { + cmd->chanlist_len = 32; + err |= -EINVAL; + } + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + /* 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 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, devpriv->mmio + BOARD_CONTROL_REG); + udelay(10); + + writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32), + devpriv->mmio + RX_PROG_ALMOST_REG); + writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32), + devpriv->mmio + TX_PROG_ALMOST_REG); + + devpriv->tx_fifo_size = readl(devpriv->mmio + TX_FIFO_SIZE_REG) & + FIFO_SIZE_MASK; + devpriv->rx_fifo_size = readl(devpriv->mmio + RX_FIFO_SIZE_REG) & + FIFO_SIZE_MASK; + + writel(0, devpriv->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); + devpriv->mmio = pci_ioremap_bar(pcidev, 2); + if (!devpriv->plx9080_mmio || !devpriv->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_WRITEABLE | 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 pci_dev *pcidev = comedi_to_pci_dev(dev); + struct hpdi_private *devpriv = dev->private; + unsigned int i; + + 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 (devpriv->mmio) + iounmap(devpriv->mmio); + /* 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); + } + comedi_pci_disable(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 00000000000..0b8b2162b76 --- /dev/null +++ b/drivers/staging/comedi/drivers/icp_multi.c @@ -0,0 +1,612 @@ +/* + 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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#define PCI_DEVICE_ID_ICP_MULTI 0x8000 + +#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 inouts */ +#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 { + char valid; /* card is usable */ + void __iomem *io_addr; /* Pointer to mapped io address */ + 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 short ao_data[4]; /* data output buffer */ + 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, + devpriv->io_addr + 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) +{ + struct icp_multi_private *devpriv = dev->private; + unsigned int status; + + status = readw(devpriv->io_addr + 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, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + 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, + devpriv->io_addr + 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(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff; + } + + /* Disable interrupt */ + devpriv->IntEnable &= ~ADC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + 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) +{ + struct icp_multi_private *devpriv = dev->private; + unsigned int status; + + status = readw(devpriv->io_addr + ICP_MULTI_DAC_CSR); + if ((status & DAC_BSY) == 0) + return 0; + return -EBUSY; +} + +static int icp_multi_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct icp_multi_private *devpriv = dev->private; + int n, chan, range; + int ret; + + /* Disable D/A conversion ready interrupt */ + devpriv->IntEnable &= ~DAC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= DAC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); + + /* Get channel number and range */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* 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, devpriv->io_addr + ICP_MULTI_DAC_CSR); + + for (n = 0; n < insn->n; n++) { + /* 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, + devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= DAC_READY; + writew(devpriv->IntStatus, + devpriv->io_addr + ICP_MULTI_INT_STAT); + + /* Clear data received */ + devpriv->ao_data[chan] = 0; + + return ret; + } + + /* Write data to analogue output data register */ + writew(data[n], devpriv->io_addr + ICP_MULTI_AO); + + /* Set DAC_ST bit to write the data to selected channel */ + devpriv->DacCmdStatus |= DAC_ST; + writew(devpriv->DacCmdStatus, + devpriv->io_addr + ICP_MULTI_DAC_CSR); + devpriv->DacCmdStatus &= ~DAC_ST; + + /* Save analogue output data */ + devpriv->ao_data[chan] = data[n]; + } + + return n; +} + +static int icp_multi_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct icp_multi_private *devpriv = dev->private; + int n, chan; + + /* Get channel number */ + chan = CR_CHAN(insn->chanspec); + + /* Read analogue outputs */ + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +static int icp_multi_insn_bits_di(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct icp_multi_private *devpriv = dev->private; + + data[1] = readw(devpriv->io_addr + 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) +{ + struct icp_multi_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) + writew(s->state, devpriv->io_addr + ICP_MULTI_DO); + + data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); + + 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; + struct icp_multi_private *devpriv = dev->private; + int int_no; + + /* Is this interrupt from our board? */ + int_no = readw(devpriv->io_addr + 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) { + comedi_error(dev, "range/channel list is empty!"); + 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)) { + comedi_error(dev, + "Incorrect differential ai ch-nr"); + return 0; + } + } else { + if (CR_CHAN(chanlist[i]) > s->n_chan) { + comedi_error(dev, + "Incorrect ai channel number"); + 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, devpriv->io_addr + ICP_MULTI_INT_EN); + writew(0x00ff, devpriv->io_addr + 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, devpriv->io_addr + ICP_MULTI_AO); + + /* Set start conversion bit */ + devpriv->DacCmdStatus |= DAC_ST; + + /* Output to command / status register */ + writew(devpriv->DacCmdStatus, + devpriv->io_addr + ICP_MULTI_DAC_CSR); + + /* Delay to allow DAC time to recover */ + udelay(1); + } + + /* Digital outputs to 0 */ + writew(0, devpriv->io_addr + 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; + + devpriv->io_addr = pci_ioremap_bar(pcidev, 2); + if (!devpriv->io_addr) + 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_insn_write_ao; + s->insn_read = icp_multi_insn_read_ao; + + 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 | SDF_READABLE; + 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 | SDF_GROUND | SDF_COMMON; + 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; + + devpriv->valid = 1; + + return 0; +} + +static void icp_multi_detach(struct comedi_device *dev) +{ + struct icp_multi_private *devpriv = dev->private; + + if (devpriv) + if (devpriv->valid) + icp_multi_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv && devpriv->io_addr) + iounmap(devpriv->io_addr); + comedi_pci_disable(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, PCI_DEVICE_ID_ICP_MULTI) }, + { 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 00000000000..2516ce83483 --- /dev/null +++ b/drivers/staging/comedi/drivers/ii_pci20kc.c @@ -0,0 +1,534 @@ +/* + * 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_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 */ + }, +}; + +struct ii20k_ao_private { + unsigned int last_data[2]; +}; + +struct ii20k_private { + void __iomem *ioaddr; +}; + +static void __iomem *ii20k_module_iobase(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ii20k_private *devpriv = dev->private; + + return devpriv->ioaddr + (s->index + 1) * II20K_MOD_OFFSET; +} + +static int ii20k_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ii20k_ao_private *ao_spriv = s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = ao_spriv->last_data[chan]; + + return insn->n; +} + +static int ii20k_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ii20k_ao_private *ao_spriv = s->private; + void __iomem *iobase = ii20k_module_iobase(dev, s); + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = ao_spriv->last_data[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + + /* 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)); + } + + ao_spriv->last_data[chan] = val; + + 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) +{ + struct ii20k_private *devpriv = dev->private; + 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, devpriv->ioaddr + II20K_CTRL01_REG); + writeb(ctrl23, devpriv->ioaddr + II20K_CTRL23_REG); + writeb(dir_ena, devpriv->ioaddr + 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) +{ + struct ii20k_private *devpriv = dev->private; + unsigned int mask; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0x000000ff) + writeb((s->state >> 0) & 0xff, + devpriv->ioaddr + II20K_DIO0_REG); + if (mask & 0x0000ff00) + writeb((s->state >> 8) & 0xff, + devpriv->ioaddr + II20K_DIO1_REG); + if (mask & 0x00ff0000) + writeb((s->state >> 16) & 0xff, + devpriv->ioaddr + II20K_DIO2_REG); + if (mask & 0xff000000) + writeb((s->state >> 24) & 0xff, + devpriv->ioaddr + II20K_DIO3_REG); + } + + data[1] = readb(devpriv->ioaddr + II20K_DIO0_REG); + data[1] |= readb(devpriv->ioaddr + II20K_DIO1_REG) << 8; + data[1] |= readb(devpriv->ioaddr + II20K_DIO2_REG) << 16; + data[1] |= readb(devpriv->ioaddr + II20K_DIO3_REG) << 24; + + return insn->n; +} + +static int ii20k_init_module(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ii20k_ao_private *ao_spriv; + void __iomem *iobase = ii20k_module_iobase(dev, s); + unsigned char id; + + id = readb(iobase + II20K_ID_REG); + switch (id) { + case II20K_ID_PCI20006M_1: + case II20K_ID_PCI20006M_2: + ao_spriv = comedi_alloc_spriv(s, sizeof(*ao_spriv)); + if (!ao_spriv) + return -ENOMEM; + + /* 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_read = ii20k_ao_insn_read; + s->insn_write = ii20k_ao_insn_write; + 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 ii20k_private *devpriv; + struct comedi_subdevice *s; + unsigned char id; + bool has_dio; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->ioaddr = (void __iomem *)(unsigned long)it->options[0]; + + id = readb(devpriv->ioaddr + 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 struct comedi_driver ii20k_driver = { + .driver_name = "ii_pci20kc", + .module = THIS_MODULE, + .attach = ii20k_attach, + .detach = comedi_legacy_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 00000000000..a8db9d86aad --- /dev/null +++ b/drivers/staging/comedi/drivers/jr3_pci.c @@ -0,0 +1,830 @@ +/* + 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 "jr3pci.idm". + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/timer.h> + +#include "../comedidev.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; + if (pos && val) { + /* Skip over non hex */ + for (; *pos < size && !isxdigit(data[*pos]); (*pos)++) + ; + /* Collect value */ + *val = 0; + for (; *pos < size; (*pos)++) { + int value; + 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 = NULL; + 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; + + init_timer(&devpriv->timer); + + 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, + "comedi/jr3pci.idm", + 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); + } + + devpriv->timer.data = (unsigned long)dev; + devpriv->timer.function = jr3_pci_poll_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"); +MODULE_FIRMWARE("comedi/jr3pci.idm"); diff --git a/drivers/staging/comedi/drivers/jr3_pci.h b/drivers/staging/comedi/drivers/jr3_pci.h new file mode 100644 index 00000000000..20478ae8fad --- /dev/null +++ b/drivers/staging/comedi/drivers/jr3_pci.h @@ -0,0 +1,681 @@ +/* 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 00000000000..ec43c38958d --- /dev/null +++ b/drivers/staging/comedi/drivers/ke_counter.c @@ -0,0 +1,181 @@ +/* + * 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 <linux/pci.h> + +#include "../comedidev.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 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 = &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); + + outb(0, dev->iobase + KE_RESET_REG(0)); + outb(0, dev->iobase + KE_RESET_REG(1)); + outb(0, dev->iobase + KE_RESET_REG(2)); + + 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_disable, +}; + +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 00000000000..25ce2f78db8 --- /dev/null +++ b/drivers/staging/comedi/drivers/me4000.c @@ -0,0 +1,1615 @@ +/* + 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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/spinlock.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "8253.h" +#include "plx9052.h" + +#if 0 +/* file removed due to GPL incompatibility */ +#include "me4000_fw.h" +#endif + +/* + * 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_BASE_FREQUENCY (unsigned int) 33E6 + +#define ME4000_AI_CHANNEL_LIST_COUNT 1024 + +struct me4000_info { + unsigned long plx_regbase; + unsigned long timer_regbase; + + unsigned int ao_readback[4]; +}; + +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) + } +}; + +#define FIRMWARE_NOT_AVAILABLE 1 +#if FIRMWARE_NOT_AVAILABLE +extern unsigned char *xilinx_firm; +#endif + +static int xilinx_download(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct me4000_info *info = dev->private; + unsigned long xilinx_iobase = pci_resource_start(pcidev, 5); + u32 value = 0; + wait_queue_head_t queue; + int idx = 0; + int size = 0; + unsigned int intcsr; + + if (!xilinx_iobase) + return -ENODEV; + + init_waitqueue_head(&queue); + + /* + * 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 */ + value = inl(info->plx_regbase + PLX9052_CNTRL); + value |= PLX9052_CNTRL_UIO2_DATA; + outl(value, info->plx_regbase + PLX9052_CNTRL); + + /* Init Xilinx with CS1 */ + inb(xilinx_iobase + 0xC8); + + /* Wait until /INIT pin is set */ + udelay(20); + intcsr = inl(info->plx_regbase + PLX9052_INTCSR); + if (!(intcsr & PLX9052_INTCSR_LI2STAT)) { + dev_err(dev->class_dev, "Can't init Xilinx\n"); + return -EIO; + } + + /* Reset /CS and /WRITE of the Xilinx */ + value = inl(info->plx_regbase + PLX9052_CNTRL); + value &= ~PLX9052_CNTRL_UIO2_DATA; + outl(value, info->plx_regbase + PLX9052_CNTRL); + if (FIRMWARE_NOT_AVAILABLE) { + dev_err(dev->class_dev, + "xilinx firmware unavailable due to licensing, aborting"); + return -EIO; + } else { + /* Download Xilinx firmware */ + size = (xilinx_firm[0] << 24) + (xilinx_firm[1] << 16) + + (xilinx_firm[2] << 8) + xilinx_firm[3]; + udelay(10); + + for (idx = 0; idx < size; idx++) { + outb(xilinx_firm[16 + idx], xilinx_iobase); + udelay(10); + + /* Check if BUSY flag is low */ + if (inl(info->plx_regbase + PLX9052_CNTRL) & PLX9052_CNTRL_UIO1_DATA) { + dev_err(dev->class_dev, + "Xilinx is still busy (idx = %d)\n", + idx); + return -EIO; + } + } + } + + /* If done flag is high download was successful */ + if (inl(info->plx_regbase + PLX9052_CNTRL) & PLX9052_CNTRL_UIO0_DATA) { + } else { + 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 */ + value = inl(info->plx_regbase + PLX9052_CNTRL); + value |= PLX9052_CNTRL_UIO2_DATA; + outl(value, 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 == SDF_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 & TRIG_ROUND_MASK) == TRIG_ROUND_NEAREST) { + if (rest > 33) + (*init_ticks)++; + } else if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_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 & TRIG_ROUND_MASK) == TRIG_ROUND_NEAREST) { + if (rest > 33) + (*scan_ticks)++; + } else if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_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 & TRIG_ROUND_MASK) == TRIG_ROUND_NEAREST) { + if (rest > 33) + (*chan_ticks)++; + } else if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_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 == SDF_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; + + /* Only rounding flags are implemented */ + cmd->flags &= TRIG_ROUND_NEAREST | TRIG_ROUND_UP | TRIG_ROUND_DOWN; + + /* 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, + TRIG_NONE | TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE | TRIG_COUNT); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_check_trigger_is_unique(cmd->scan_end_src); + err |= cfc_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 |= cfc_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 (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->stop_src == TRIG_COUNT) { + if (cmd->stop_arg == 0) { + dev_err(dev->class_dev, "Invalid stop arg\n"); + cmd->stop_arg = 1; + 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 | COMEDI_CB_EOA; + + 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)) { + s->async->events |= COMEDI_CB_BLOCK; + + 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 | COMEDI_CB_EOA; + + 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_put(s, lval)) { + /* + * 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); + + s->async->events |= COMEDI_CB_OVERFLOW; + + dev_err(dev->class_dev, "Buffer overflow\n"); + + 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_BLOCK | 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_put(s, lval)) { + dev_err(dev->class_dev, "Buffer overflow\n"); + s->async->events |= COMEDI_CB_OVERFLOW; + 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); + } + + if (s->async->events) + comedi_event(dev, s); + + return IRQ_HANDLED; +} + +/*============================================================================= + Analog output section + ===========================================================================*/ + +static int me4000_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct me4000_board *thisboard = comedi_board(dev); + struct me4000_info *info = dev->private; + int chan = CR_CHAN(insn->chanspec); + int rang = CR_RANGE(insn->chanspec); + int aref = CR_AREF(insn->chanspec); + unsigned int tmp; + + 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; + } + + if (chan >= thisboard->ao_nchan) { + dev_err(dev->class_dev, "Invalid channel %d\n", insn->n); + return -EINVAL; + } + + if (rang != 0) { + dev_err(dev->class_dev, "Invalid range %d\n", insn->n); + return -EINVAL; + } + + if (aref != AREF_GROUND && aref != AREF_COMMON) { + dev_err(dev->class_dev, "Invalid aref %d\n", insn->n); + return -EINVAL; + } + + /* 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 */ + info->ao_readback[chan] = data[0]; + + return 1; +} + +static int me4000_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct me4000_info *info = dev->private; + int chan = CR_CHAN(insn->chanspec); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + dev_err(dev->class_dev, "Invalid instruction length\n"); + return -EINVAL; + } + + data[0] = info->ao_readback[chan]; + + 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; +} + +/*============================================================================= + Counter section + ===========================================================================*/ + +static int me4000_cnt_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct me4000_info *info = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int err; + + switch (data[0]) { + case GPCT_RESET: + if (insn->n != 1) + return -EINVAL; + + err = i8254_set_mode(info->timer_regbase, 0, chan, + I8254_MODE0 | I8254_BINARY); + if (err) + return err; + i8254_write(info->timer_regbase, 0, chan, 0); + break; + case GPCT_SET_OPERATION: + if (insn->n != 2) + return -EINVAL; + + err = i8254_set_mode(info->timer_regbase, 0, chan, + (data[1] << 1) | I8254_BINARY); + if (err) + return err; + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int me4000_cnt_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct me4000_info *info = dev->private; + + if (insn->n == 0) + return 0; + + if (insn->n > 1) { + dev_err(dev->class_dev, "Invalid instruction length %d\n", + insn->n); + return -EINVAL; + } + + data[0] = i8254_read(info->timer_regbase, 0, insn->chanspec); + + return 1; +} + +static int me4000_cnt_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct me4000_info *info = dev->private; + + 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; + } + + i8254_write(info->timer_regbase, 0, insn->chanspec, data[0]); + + return 1; +} + +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); + info->timer_regbase = pci_resource_start(pcidev, 3); + if (!info->plx_regbase || !dev->iobase || !info->timer_regbase) + return -ENODEV; + + result = xilinx_download(dev); + if (result) + 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_WRITEABLE | 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; + s->insn_read = me4000_ao_insn_read; + } 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 + ========================================================================*/ + + s = &dev->subdevices[3]; + + if (thisboard->has_counter) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 3; + s->maxdata = 0xFFFF; /* 16 bit counters */ + s->insn_read = me4000_cnt_insn_read; + s->insn_write = me4000_cnt_insn_write; + s->insn_config = me4000_cnt_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static void me4000_detach(struct comedi_device *dev) +{ + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + me4000_reset(dev); + comedi_pci_disable(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"); diff --git a/drivers/staging/comedi/drivers/me_daq.c b/drivers/staging/comedi/drivers/me_daq.c new file mode 100644 index 00000000000..0ff126b1fdf --- /dev/null +++ b/drivers/staging/comedi/drivers/me_daq.c @@ -0,0 +1,603 @@ +/* + * 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] + * (Meilhaus) 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/pci.h> +#include <linux/interrupt.h> +#include <linux/sched.h> + +#include "../comedidev.h" + +#include "plx9052.h" + +#define ME2600_FIRMWARE "me2600_firmware.bin" + +#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 */ + void __iomem *me_regbase; /* Base address of the Meilhaus card */ + + 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 */ + int ao_readback[4]; /* Mirror of analog output data */ +}; + +static inline void sleep(unsigned sec) +{ + 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, devpriv->me_regbase + 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) +{ + struct me_private_data *dev_private = dev->private; + void __iomem *mmio_porta = dev_private->me_regbase + ME_DIO_PORT_A; + void __iomem *mmio_portb = dev_private->me_regbase + 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) +{ + struct me_private_data *dev_private = dev->private; + unsigned int status; + + status = readw(dev_private->me_regbase + 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_private->me_regbase + ME_CONTROL_1); + + /* clear chanlist and ad fifo */ + dev_private->control_2 &= ~(ENABLE_ADFIFO | ENABLE_CHANLIST); + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* reset any pending interrupt */ + writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); + + /* enable the chanlist and ADC fifo */ + dev_private->control_2 |= (ENABLE_ADFIFO | ENABLE_CHANLIST); + writew(dev_private->control_2, dev_private->me_regbase + 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_private->me_regbase + ME_CHANNEL_LIST); + + /* set ADC mode to software trigger */ + dev_private->control_1 |= SOFTWARE_TRIGGERED_ADC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + /* start conversion by reading from ADC_START */ + readw(dev_private->me_regbase + 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_private->me_regbase + 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_private->me_regbase + 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); + int i; + + /* Enable all DAC */ + dev_private->control_2 |= ENABLE_DAC; + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* and set DAC to "buffered" mode */ + dev_private->control_2 |= BUFFERED_DAC; + writew(dev_private->control_2, dev_private->me_regbase + 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_private->me_regbase + ME_DAC_CONTROL); + + /* Update dac-control register */ + readw(dev_private->me_regbase + ME_DAC_CONTROL_UPDATE); + + /* Set data register */ + for (i = 0; i < insn->n; i++) { + writew((data[0] & s->maxdata), + dev_private->me_regbase + ME_DAC_DATA_A + (chan << 1)); + dev_private->ao_readback[chan] = (data[0] & s->maxdata); + } + + /* Update dac with data registers */ + readw(dev_private->me_regbase + ME_DAC_UPDATE); + + return insn->n; +} + +static int me_ao_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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = dev_private->ao_readback[chan]; + + 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_private->me_regbase + XILINX_DOWNLOAD_RESET); + + /* Wait until reset is over */ + sleep(1); + + /* Write a dummy value to Xilinx */ + writeb(0x00, dev_private->me_regbase + 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_private->me_regbase + 0x0); + + /* Write 5 dummy values to xilinx */ + for (i = 0; i < 5; i++) + writeb(0x00, dev_private->me_regbase + 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_private->me_regbase + ME_CONTROL_1); + writew(0x00, dev_private->me_regbase + ME_CONTROL_2); + writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); + writew(0x00, dev_private->me_regbase + 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_private->me_regbase = pci_ioremap_bar(pcidev, 2); + if (!dev_private->me_regbase) + 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_WRITEABLE | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->len_chanlist = 4; + s->range_table = &me_ao_range; + s->insn_read = me_ao_insn_read; + s->insn_write = me_ao_insn_write; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITEABLE; + 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_private->me_regbase) { + me_reset(dev); + iounmap(dev_private->me_regbase); + } + if (dev_private->plx_regbase) + iounmap(dev_private->plx_regbase); + } + comedi_pci_disable(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"); +MODULE_FIRMWARE(ME2600_FIRMWARE); diff --git a/drivers/staging/comedi/drivers/mf6x4.c b/drivers/staging/comedi/drivers/mf6x4.c new file mode 100644 index 00000000000..a4f7d6f138d --- /dev/null +++ b/drivers/staging/comedi/drivers/mf6x4.c @@ -0,0 +1,351 @@ +/* + * 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, Humusoft MF624 + * Author: Rostislav Lisovy <lisovy@gmail.com> + * Status: works + * Updated: + * Configuration Options: none + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include "../comedidev.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)) +#define MF6X4_DA_M 0x3fff + +/* 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 *bar1_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; + + /* DAC value cache -- used for insn_read function */ + int ao_readback[8]; +}; + +static int mf6x4_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct mf6x4_private *devpriv = dev->private; + + data[1] = ioread16(devpriv->bar1_mem + 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) +{ + struct mf6x4_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) + iowrite16(s->state & MF6X4_DOUT_M, + devpriv->bar1_mem + 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) +{ + struct mf6x4_private *devpriv = dev->private; + 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, + devpriv->bar1_mem + MF6X4_ADCTRL_R); + + for (i = 0; i < insn->n; i++) { + /* Trigger ADC conversion by reading ADSTART */ + ioread16(devpriv->bar1_mem + MF6X4_ADSTART_R); + + ret = comedi_timeout(dev, s, insn, mf6x4_ai_eoc, 0); + if (ret) + return ret; + + /* Read the actual value */ + d = ioread16(devpriv->bar1_mem + MF6X4_ADDATA_R); + d &= s->maxdata; + data[i] = d; + } + + iowrite16(0x0, devpriv->bar1_mem + 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); + 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++) { + iowrite16(data[i] & MF6X4_DA_M, + devpriv->bar1_mem + MF6X4_DAC_R(chan)); + + devpriv->ao_readback[chan] = data[i]; + } + + return insn->n; +} + +static int mf6x4_ao_insn_read(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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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; + + devpriv->bar1_mem = pci_ioremap_bar(pcidev, board->bar_nums[1]); + if (!devpriv->bar1_mem) + 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; + s->insn_read = mf6x4_ao_insn_read; + + /* 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->bar0_mem) + iounmap(devpriv->bar0_mem); + if (devpriv->bar1_mem) + iounmap(devpriv->bar1_mem); + if (devpriv->bar2_mem) + iounmap(devpriv->bar2_mem); + + comedi_pci_disable(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 00000000000..19c029acbc9 --- /dev/null +++ b/drivers/staging/comedi/drivers/mite.c @@ -0,0 +1,643 @@ +/* + 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 <linux/pci.h> + +#include "../comedidev.h" + +#include "comedi_fc.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 mite_struct *mite, unsigned use_iodwbsr_1) +{ + 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(&mite->pcidev->dev, + "Failed to remap mite io memory address\n"); + return -ENOMEM; + } + mite->mite_phys_addr = pci_resource_start(mite->pcidev, 0); + + mite->daq_io_addr = pci_ioremap_bar(mite->pcidev, 1); + if (!mite->daq_io_addr) { + dev_err(&mite->pcidev->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_iodwbsr_1) { + writel(0, mite->mite_io_addr + MITE_IODWBSR); + dev_info(&mite->pcidev->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(&mite->pcidev->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(&mite->pcidev->dev, "fifo size is %i.\n", mite->fifo_size); + return 0; +} +EXPORT_SYMBOL_GPL(mite_setup2); + +int mite_setup(struct mite_struct *mite) +{ + return mite_setup2(mite, 0); +} +EXPORT_SYMBOL_GPL(mite_setup); + +void mite_unsetup(struct mite_struct *mite) +{ + /* unsigned long offset, start, length; */ + + if (!mite) + return; + + if (mite->mite_io_addr) { + iounmap(mite->mite_io_addr); + mite->mite_io_addr = NULL; + } + if (mite->daq_io_addr) { + iounmap(mite->daq_io_addr); + mite->daq_io_addr = NULL; + } + if (mite->mite_phys_addr) + mite->mite_phys_addr = 0; +} +EXPORT_SYMBOL_GPL(mite_unsetup); + +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 == NULL) + return ring; + ring->hw_dev = get_device(&mite->pcidev->dev); + if (ring->hw_dev == NULL) { + 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; + int tcr; + int lkar; + + lkar = readl(mite->mite_io_addr + MITE_LKAR(mite_chan->channel)); + tcr = readl(mite->mite_io_addr + MITE_TCR(mite_chan->channel)); + + return tcr; +} +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); + cfc_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 * cfc_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 00000000000..e6e58e989b7 --- /dev/null +++ b/drivers/staging/comedi/drivers/mite.h @@ -0,0 +1,430 @@ +/* + 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/pci.h> +#include <linux/log2.h> +#include <linux/slab.h> +#include "../comedidev.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; + void __iomem *daq_io_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); + +static inline void mite_free(struct mite_struct *mite) +{ + kfree(mite); +} + +static inline unsigned int mite_irq(struct mite_struct *mite) +{ + return mite->pcidev->irq; +}; + +static inline unsigned int mite_device_id(struct mite_struct *mite) +{ + return mite->pcidev->device; +}; + +int mite_setup(struct mite_struct *mite); +int mite_setup2(struct mite_struct *mite, unsigned use_iodwbsr_1); +void mite_unsetup(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); + +static inline int CHAN_OFFSET(int channel) +{ + return 0x500 + 0x100 * channel; +}; + +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 */ +}; +static inline int MITE_CHOR(int channel) +{ /* channel operation */ + return CHAN_OFFSET(channel) + 0x0; +}; + +static inline int MITE_CHCR(int channel) +{ /* channel control */ + return CHAN_OFFSET(channel) + 0x4; +}; + +static inline int MITE_TCR(int channel) +{ /* transfer count */ + return CHAN_OFFSET(channel) + 0x8; +}; + +static inline int MITE_MCR(int channel) +{ /* memory configuration */ + return CHAN_OFFSET(channel) + 0xc; +}; + +static inline int MITE_MAR(int channel) +{ /* memory address */ + return CHAN_OFFSET(channel) + 0x10; +}; + +static inline int MITE_DCR(int channel) +{ /* device configuration */ + return CHAN_OFFSET(channel) + 0x14; +}; + +static inline int MITE_DAR(int channel) +{ /* device address */ + return CHAN_OFFSET(channel) + 0x18; +}; + +static inline int MITE_LKCR(int channel) +{ /* link configuration */ + return CHAN_OFFSET(channel) + 0x1c; +}; + +static inline int MITE_LKAR(int channel) +{ /* link address */ + return CHAN_OFFSET(channel) + 0x20; +}; + +static inline int MITE_LLKAR(int channel) +{ /* see mite section of tnt5002 manual */ + return CHAN_OFFSET(channel) + 0x24; +}; + +static inline int MITE_BAR(int channel) +{ /* base address */ + return CHAN_OFFSET(channel) + 0x28; +}; + +static inline int MITE_BCR(int channel) +{ /* base count */ + return CHAN_OFFSET(channel) + 0x2c; +}; + +static inline int MITE_SAR(int channel) +{ /* ? address */ + return CHAN_OFFSET(channel) + 0x30; +}; + +static inline int MITE_WSCR(int channel) +{ /* ? */ + return CHAN_OFFSET(channel) + 0x34; +}; + +static inline int MITE_WSER(int channel) +{ /* ? */ + return CHAN_OFFSET(channel) + 0x38; +}; + +static inline int MITE_CHSR(int channel) +{ /* channel status */ + return CHAN_OFFSET(channel) + 0x3c; +}; + +static inline int MITE_FCR(int channel) +{ /* fifo count */ + return CHAN_OFFSET(channel) + 0x40; +}; + +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; + if (wpdep_bits == 0) + return 0; + else + return 1 << (wpdep_bits - 1); +}; + +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 00000000000..f770400a0e8 --- /dev/null +++ b/drivers/staging/comedi/drivers/mpc624.c @@ -0,0 +1,361 @@ +/* + 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> + +/* Consecutive I/O port addresses */ +#define MPC624_SIZE 16 + +/* 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], MPC624_SIZE); + 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 00000000000..b74b9e9bfd4 --- /dev/null +++ b/drivers/staging/comedi/drivers/multiq3.c @@ -0,0 +1,310 @@ +/* + 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" + +#define MULTIQ3_SIZE 16 + +/* + * 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 + +struct multiq3_private { + unsigned int ao_readback[2]; +}; + +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_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct multiq3_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 multiq3_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct multiq3_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + outw(MULTIQ3_CONTROL_MUST | MULTIQ3_DA_LOAD | chan, + dev->iobase + MULTIQ3_CONTROL); + outw(data[i], dev->iobase + MULTIQ3_DAC_DATA); + outw(MULTIQ3_CONTROL_MUST, dev->iobase + MULTIQ3_CONTROL); + + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +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 n; + int chan = CR_CHAN(insn->chanspec); + int control = MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3); + + for (n = 0; n < insn->n; n++) { + int value; + 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 multiq3_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], MULTIQ3_SIZE); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 5); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + 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->insn_read = multiq3_ao_insn_read; + s->insn_write = multiq3_ao_insn_write; + s->maxdata = 0xfff; + s->range_table = &range_bipolar5; + + 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 00000000000..c8b1fa793a3 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_6527.c @@ -0,0 +1,476 @@ +/* + * 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] + * (National Instruments) 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/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.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 { + void __iomem *mmio_base; + 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; + void __iomem *mmio = devpriv->mmio_base; + + if (val != devpriv->filter_interval) { + writeb(val & 0xff, mmio + NI6527_FILT_INTERVAL_REG(0)); + writeb((val >> 8) & 0xff, mmio + NI6527_FILT_INTERVAL_REG(1)); + writeb((val >> 16) & 0x0f, mmio + NI6527_FILT_INTERVAL_REG(2)); + + writeb(NI6527_CLR_INTERVAL, mmio + NI6527_CLR_REG); + + devpriv->filter_interval = val; + } +} + +static void ni6527_set_filter_enable(struct comedi_device *dev, + unsigned int val) +{ + struct ni6527_private *devpriv = dev->private; + void __iomem *mmio = devpriv->mmio_base; + + writeb(val & 0xff, mmio + NI6527_FILT_ENA_REG(0)); + writeb((val >> 8) & 0xff, mmio + NI6527_FILT_ENA_REG(1)); + writeb((val >> 16) & 0xff, 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) +{ + struct ni6527_private *devpriv = dev->private; + void __iomem *mmio = devpriv->mmio_base; + unsigned int val; + + val = readb(mmio + NI6527_DI_REG(0)); + val |= (readb(mmio + NI6527_DI_REG(1)) << 8); + val |= (readb(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) +{ + struct ni6527_private *devpriv = dev->private; + void __iomem *mmio = devpriv->mmio_base; + 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, mmio + NI6527_DO_REG(0)); + if (mask & 0x00ff00) + writeb((val >> 8) & 0xff, mmio + NI6527_DO_REG(1)); + if (mask & 0xff0000) + writeb((val >> 16) & 0xff, 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 ni6527_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + void __iomem *mmio = devpriv->mmio_base; + unsigned int status; + + status = readb(mmio + NI6527_STATUS_REG); + if (!(status & NI6527_STATUS_IRQ)) + return IRQ_NONE; + + if (status & NI6527_STATUS_EDGE) { + comedi_buf_put(s, 0); + s->async->events |= COMEDI_CB_EOS; + comedi_event(dev, s); + } + + writeb(NI6527_CLR_IRQS, 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_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 ni6527_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni6527_private *devpriv = dev->private; + void __iomem *mmio = devpriv->mmio_base; + + writeb(NI6527_CLR_IRQS, mmio + NI6527_CLR_REG); + writeb(NI6527_CTRL_ENABLE_IRQS, mmio + NI6527_CTRL_REG); + + return 0; +} + +static int ni6527_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni6527_private *devpriv = dev->private; + void __iomem *mmio = devpriv->mmio_base; + + writeb(NI6527_CTRL_DISABLE_IRQS, 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 rising, + unsigned int falling) +{ + struct ni6527_private *devpriv = dev->private; + void __iomem *mmio = devpriv->mmio_base; + + /* enable rising-edge detection channels */ + writeb(rising & 0xff, mmio + NI6527_RISING_EDGE_REG(0)); + writeb((rising >> 8) & 0xff, mmio + NI6527_RISING_EDGE_REG(1)); + writeb((rising >> 16) & 0xff, mmio + NI6527_RISING_EDGE_REG(2)); + + /* enable falling-edge detection channels */ + writeb(falling & 0xff, mmio + NI6527_FALLING_EDGE_REG(0)); + writeb((falling >> 8) & 0xff, mmio + NI6527_FALLING_EDGE_REG(1)); + writeb((falling >> 16) & 0xff, mmio + NI6527_FALLING_EDGE_REG(2)); +} + +static int ni6527_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: + /* check_insn_config_length() does not check this instruction */ + if (insn->n != 3) + return -EINVAL; + ni6527_set_edge_detection(dev, data[1], data[2]); + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static void ni6527_reset(struct comedi_device *dev) +{ + struct ni6527_private *devpriv = dev->private; + void __iomem *mmio = devpriv->mmio_base; + + /* disable deglitch filters on all channels */ + ni6527_set_filter_enable(dev, 0); + + writeb(NI6527_CLR_IRQS | NI6527_CLR_RESET_FILT, + mmio + NI6527_CLR_REG); + writeb(NI6527_CTRL_DISABLE_IRQS, 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; + + devpriv->mmio_base = pci_ioremap_bar(pcidev, 1); + if (!devpriv->mmio_base) + return -ENOMEM; + + /* make sure this is actually a 6527 device */ + if (readb(devpriv->mmio_base + 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) +{ + struct ni6527_private *devpriv = dev->private; + + if (devpriv && devpriv->mmio_base) + ni6527_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(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 00000000000..9a139d6b8ef --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_65xx.c @@ -0,0 +1,778 @@ +/* + comedi/drivers/ni_6514.c + driver for National Instruments PCI-6514 + + 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 (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 +Updated: Wed Oct 18 08:59:11 EDT 2006 + +Based on the PCI-6527 driver by ds. +The interrupt subdevice (subdevice 3) is probably broken for all boards +except maybe the 6514. + +*/ + +/* + Manuals (available from ftp://ftp.natinst.com/support/manuals) + + 370106b.pdf 6514 Register Level Programmer Manual + + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "mite.h" + +#define NI6514_DIO_SIZE 4096 +#define NI6514_MITE_SIZE 4096 + +#define NI_65XX_MAX_NUM_PORTS 12 +static const unsigned ni_65xx_channels_per_port = 8; +static const unsigned ni_65xx_port_offset = 0x10; + +static inline unsigned Port_Data(unsigned port) +{ + return 0x40 + port * ni_65xx_port_offset; +} + +static inline unsigned Port_Select(unsigned port) +{ + return 0x41 + port * ni_65xx_port_offset; +} + +static inline unsigned Rising_Edge_Detection_Enable(unsigned port) +{ + return 0x42 + port * ni_65xx_port_offset; +} + +static inline unsigned Falling_Edge_Detection_Enable(unsigned port) +{ + return 0x43 + port * ni_65xx_port_offset; +} + +static inline unsigned Filter_Enable(unsigned port) +{ + return 0x44 + port * ni_65xx_port_offset; +} + +#define ID_Register 0x00 + +#define Clear_Register 0x01 +#define ClrEdge 0x08 +#define ClrOverflow 0x04 + +#define Filter_Interval 0x08 + +#define Change_Status 0x02 +#define MasterInterruptStatus 0x04 +#define Overflow 0x02 +#define EdgeStatus 0x01 + +#define Master_Interrupt_Control 0x03 +#define FallingEdgeIntEnable 0x10 +#define RisingEdgeIntEnable 0x08 +#define MasterInterruptEnable 0x04 +#define OverflowIntEnable 0x02 +#define EdgeIntEnable 0x01 + +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 invert_outputs: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, + .invert_outputs = 1, + }, + [BOARD_PXI6513] = { + .name = "pxi-6513", + .num_do_ports = 8, + .invert_outputs = 1, + }, + [BOARD_PCI6514] = { + .name = "pci-6514", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1, + }, + [BOARD_PXI6514] = { + .name = "pxi-6514", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1, + }, + [BOARD_PCI6515] = { + .name = "pci-6515", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1, + }, + [BOARD_PXI6515] = { + .name = "pxi-6515", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1, + }, + [BOARD_PCI6516] = { + .name = "pci-6516", + .num_do_ports = 4, + .invert_outputs = 1, + }, + [BOARD_PCI6517] = { + .name = "pci-6517", + .num_do_ports = 4, + .invert_outputs = 1, + }, + [BOARD_PCI6518] = { + .name = "pci-6518", + .num_di_ports = 2, + .num_do_ports = 2, + .invert_outputs = 1, + }, + [BOARD_PCI6519] = { + .name = "pci-6519", + .num_di_ports = 2, + .num_do_ports = 2, + .invert_outputs = 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 inline unsigned ni_65xx_port_by_channel(unsigned channel) +{ + return channel / ni_65xx_channels_per_port; +} + +static inline unsigned ni_65xx_total_num_ports(const struct ni_65xx_board + *board) +{ + return board->num_dio_ports + board->num_di_ports + board->num_do_ports; +} + +struct ni_65xx_private { + struct mite_struct *mite; + unsigned int filter_interval; + unsigned short filter_enable[NI_65XX_MAX_NUM_PORTS]; + unsigned short output_bits[NI_65XX_MAX_NUM_PORTS]; + unsigned short dio_direction[NI_65XX_MAX_NUM_PORTS]; +}; + +struct ni_65xx_subdevice_private { + unsigned base_port; +}; + +static inline struct ni_65xx_subdevice_private *sprivate(struct comedi_subdevice + *subdev) +{ + return subdev->private; +} + +static int ni_65xx_config_filter(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct ni_65xx_private *devpriv = dev->private; + const unsigned chan = CR_CHAN(insn->chanspec); + const unsigned port = + sprivate(s)->base_port + ni_65xx_port_by_channel(chan); + + if (data[0] != INSN_CONFIG_FILTER) + return -EINVAL; + if (data[1]) { + static const unsigned filter_resolution_ns = 200; + static const unsigned max_filter_interval = 0xfffff; + unsigned interval = + (data[1] + + (filter_resolution_ns / 2)) / filter_resolution_ns; + if (interval > max_filter_interval) + interval = max_filter_interval; + data[1] = interval * filter_resolution_ns; + + if (interval != devpriv->filter_interval) { + writeb(interval, + devpriv->mite->daq_io_addr + + Filter_Interval); + devpriv->filter_interval = interval; + } + + devpriv->filter_enable[port] |= + 1 << (chan % ni_65xx_channels_per_port); + } else { + devpriv->filter_enable[port] &= + ~(1 << (chan % ni_65xx_channels_per_port)); + } + + writeb(devpriv->filter_enable[port], + devpriv->mite->daq_io_addr + Filter_Enable(port)); + + return 2; +} + +static int ni_65xx_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct ni_65xx_private *devpriv = dev->private; + unsigned port; + + if (insn->n < 1) + return -EINVAL; + port = sprivate(s)->base_port + + ni_65xx_port_by_channel(CR_CHAN(insn->chanspec)); + switch (data[0]) { + case INSN_CONFIG_FILTER: + return ni_65xx_config_filter(dev, s, insn, data); + break; + case INSN_CONFIG_DIO_OUTPUT: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + devpriv->dio_direction[port] = COMEDI_OUTPUT; + writeb(0, devpriv->mite->daq_io_addr + Port_Select(port)); + return 1; + break; + case INSN_CONFIG_DIO_INPUT: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + devpriv->dio_direction[port] = COMEDI_INPUT; + writeb(1, devpriv->mite->daq_io_addr + Port_Select(port)); + return 1; + break; + case INSN_CONFIG_DIO_QUERY: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + data[1] = devpriv->dio_direction[port]; + return insn->n; + break; + default: + break; + } + return -EINVAL; +} + +static int ni_65xx_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct ni_65xx_board *board = comedi_board(dev); + struct ni_65xx_private *devpriv = dev->private; + int base_bitfield_channel; + unsigned read_bits = 0; + int last_port_offset = ni_65xx_port_by_channel(s->n_chan - 1); + int port_offset; + + base_bitfield_channel = CR_CHAN(insn->chanspec); + for (port_offset = ni_65xx_port_by_channel(base_bitfield_channel); + port_offset <= last_port_offset; port_offset++) { + unsigned port = sprivate(s)->base_port + port_offset; + int base_port_channel = port_offset * ni_65xx_channels_per_port; + unsigned port_mask, port_data, port_read_bits; + int bitshift = base_port_channel - base_bitfield_channel; + + 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; + if (port_mask) { + unsigned bits; + devpriv->output_bits[port] &= ~port_mask; + devpriv->output_bits[port] |= + port_data & port_mask; + bits = devpriv->output_bits[port]; + if (board->invert_outputs) + bits = ~bits; + writeb(bits, + devpriv->mite->daq_io_addr + + Port_Data(port)); + } + port_read_bits = + readb(devpriv->mite->daq_io_addr + Port_Data(port)); + if (s->type == COMEDI_SUBD_DO && board->invert_outputs) { + /* Outputs inverted, so invert value read back from + * DO subdevice. (Does not apply to boards with DIO + * subdevice.) */ + port_read_bits ^= 0xFF; + } + if (bitshift > 0) + port_read_bits <<= bitshift; + else + port_read_bits >>= -bitshift; + + read_bits |= port_read_bits; + } + data[1] = read_bits; + return insn->n; +} + +static irqreturn_t ni_65xx_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct ni_65xx_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + + status = readb(devpriv->mite->daq_io_addr + Change_Status); + if ((status & MasterInterruptStatus) == 0) + return IRQ_NONE; + if ((status & EdgeStatus) == 0) + return IRQ_NONE; + + writeb(ClrEdge | ClrOverflow, + devpriv->mite->daq_io_addr + Clear_Register); + + comedi_buf_put(s, 0); + s->async->events |= COMEDI_CB_EOS; + comedi_event(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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_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 ni_65xx_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_65xx_private *devpriv = dev->private; + + writeb(ClrEdge | ClrOverflow, + devpriv->mite->daq_io_addr + Clear_Register); + writeb(FallingEdgeIntEnable | RisingEdgeIntEnable | + MasterInterruptEnable | EdgeIntEnable, + devpriv->mite->daq_io_addr + Master_Interrupt_Control); + + return 0; +} + +static int ni_65xx_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_65xx_private *devpriv = dev->private; + + writeb(0x00, devpriv->mite->daq_io_addr + Master_Interrupt_Control); + + 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) +{ + struct ni_65xx_private *devpriv = dev->private; + + if (insn->n < 1) + return -EINVAL; + if (data[0] != INSN_CONFIG_CHANGE_NOTIFY) + return -EINVAL; + + writeb(data[1], + devpriv->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0)); + writeb(data[1] >> 8, + devpriv->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0x10)); + writeb(data[1] >> 16, + devpriv->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0x20)); + writeb(data[1] >> 24, + devpriv->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0x30)); + + writeb(data[2], + devpriv->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0)); + writeb(data[2] >> 8, + devpriv->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0x10)); + writeb(data[2] >> 16, + devpriv->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0x20)); + writeb(data[2] >> 24, + devpriv->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0x30)); + + return 2; +} + +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 ni_65xx_private *devpriv; + struct ni_65xx_subdevice_private *spriv; + 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; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->mite = mite_alloc(pcidev); + if (!devpriv->mite) + return -ENOMEM; + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + dev_warn(dev->class_dev, "error setting up mite\n"); + return ret; + } + + dev->irq = mite_irq(devpriv->mite); + dev_info(dev->class_dev, "board: %s, ID=0x%02x", dev->board_name, + readb(devpriv->mite->daq_io_addr + ID_Register)); + + 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 = + board->num_di_ports * ni_65xx_channels_per_port; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_config = ni_65xx_dio_insn_config; + s->insn_bits = ni_65xx_dio_insn_bits; + spriv = comedi_alloc_spriv(s, sizeof(*spriv)); + if (!spriv) + return -ENOMEM; + spriv->base_port = 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_READABLE | SDF_WRITABLE; + s->n_chan = + board->num_do_ports * ni_65xx_channels_per_port; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = ni_65xx_dio_insn_bits; + spriv = comedi_alloc_spriv(s, sizeof(*spriv)); + if (!spriv) + return -ENOMEM; + spriv->base_port = board->num_di_ports; + } 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 = + board->num_dio_ports * ni_65xx_channels_per_port; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_config = ni_65xx_dio_insn_config; + s->insn_bits = ni_65xx_dio_insn_bits; + spriv = comedi_alloc_spriv(s, sizeof(*spriv)); + if (!spriv) + return -ENOMEM; + spriv->base_port = 0; + for (i = 0; i < board->num_dio_ports; ++i) { + /* configure all ports for input */ + writeb(0x1, + devpriv->mite->daq_io_addr + + Port_Select(i)); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + 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->range_table = &range_unknown; + s->maxdata = 1; + s->len_chanlist = 1; + s->do_cmdtest = ni_65xx_intr_cmdtest; + s->do_cmd = ni_65xx_intr_cmd; + s->cancel = ni_65xx_intr_cancel; + s->insn_bits = ni_65xx_intr_insn_bits; + s->insn_config = ni_65xx_intr_insn_config; + + for (i = 0; i < ni_65xx_total_num_ports(board); ++i) { + writeb(0x00, + devpriv->mite->daq_io_addr + Filter_Enable(i)); + if (board->invert_outputs) + writeb(0x01, + devpriv->mite->daq_io_addr + Port_Data(i)); + else + writeb(0x00, + devpriv->mite->daq_io_addr + Port_Data(i)); + } + writeb(ClrEdge | ClrOverflow, + devpriv->mite->daq_io_addr + Clear_Register); + writeb(0x00, + devpriv->mite->daq_io_addr + Master_Interrupt_Control); + + /* Set filter interval to 0 (32bit reg) */ + writeb(0x00000000, devpriv->mite->daq_io_addr + Filter_Interval); + + ret = request_irq(dev->irq, ni_65xx_interrupt, IRQF_SHARED, + "ni_65xx", dev); + if (ret < 0) { + dev->irq = 0; + dev_warn(dev->class_dev, "irq not available\n"); + } + + return 0; +} + +static void ni_65xx_detach(struct comedi_device *dev) +{ + struct ni_65xx_private *devpriv = dev->private; + + if (devpriv && devpriv->mite && devpriv->mite->daq_io_addr) { + writeb(0x00, + devpriv->mite->daq_io_addr + + Master_Interrupt_Control); + } + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->mite) { + mite_unsetup(devpriv->mite); + mite_free(devpriv->mite); + } + } + comedi_pci_disable(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 low-level driver"); +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 00000000000..634cde83a02 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_660x.c @@ -0,0 +1,1240 @@ +/* + 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/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.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) +{ + if (pfi_channel % 2) + return 0; + else + return 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 = comedi_board(dev); + + 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) +{ + struct ni_660x_private *devpriv = dev->private; + void __iomem *write_address = + devpriv->mite->daq_io_addr + GPCT_OFFSET[chip] + + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + writew(bits, write_address); + break; + case DATA_4B: + writel(bits, write_address); + break; + default: + BUG(); + break; + } +} + +static inline unsigned ni_660x_read_register(struct comedi_device *dev, + unsigned chip, + enum ni_660x_register reg) +{ + struct ni_660x_private *devpriv = dev->private; + void __iomem *read_address = + devpriv->mite->daq_io_addr + GPCT_OFFSET[chip] + + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + return readw(read_address); + break; + case DATA_4B: + return readl(read_address); + break; + 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 == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for counter."); + 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) { + comedi_error(dev, + "no dma channel available for use by counter"); + return retval; + } + ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL, NULL); + + 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); + cfc_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, + unsigned long new_size) +{ + 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 = comedi_board(dev); + 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] == NULL) + return -ENOMEM; + } + } + return 0; +} + +static void ni_660x_free_mite_rings(struct comedi_device *dev) +{ + const struct ni_660x_board *board = comedi_board(dev); + 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 = comedi_board(dev); + 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(devpriv->mite, 1); + if (ret < 0) { + dev_warn(dev->class_dev, "error setting up mite\n"); + 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 == NULL) + 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 /* | SDF_CMD_WRITE */ ; + 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(mite_irq(devpriv->mite), ni_660x_interrupt, + IRQF_SHARED, "ni_660x", dev); + if (ret < 0) { + dev_warn(dev->class_dev, " irq not available\n"); + return ret; + } + dev->irq = mite_irq(devpriv->mite); + 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); + if (devpriv->mite) { + ni_660x_free_mite_rings(dev); + mite_unsetup(devpriv->mite); + mite_free(devpriv->mite); + } + } + 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 00000000000..1002ceacfdc --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_670x.c @@ -0,0 +1,300 @@ +/* + 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/pci.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "mite.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 { + + struct mite_struct *mite; + int boardtype; + int dio; + unsigned int ao_readback[32]; +}; + +static int ni_670x_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct ni_670x_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + + /* 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++) { + /* First write in channel register which channel to use */ + writel(((chan & 15) << 1) | ((chan & 16) >> 4), + devpriv->mite->daq_io_addr + AO_CHAN_OFFSET); + /* write channel value */ + writel(data[i], devpriv->mite->daq_io_addr + AO_VALUE_OFFSET); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int ni_670x_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct ni_670x_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 ni_670x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_670x_private *devpriv = dev->private; + void __iomem *io_addr = devpriv->mite->daq_io_addr + + DIO_PORT0_DATA_OFFSET; + + if (comedi_dio_update_state(s, data)) + writel(s->state, io_addr); + + data[1] = readl(io_addr); + + 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) +{ + struct ni_670x_private *devpriv = dev->private; + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + writel(s->io_bits, devpriv->mite->daq_io_addr + DIO_PORT0_DIR_OFFSET); + + return insn->n; +} + +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; + + devpriv->mite = mite_alloc(pcidev); + if (!devpriv->mite) + return -ENOMEM; + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + dev_warn(dev->class_dev, "error setting up mite\n"); + return ret; + } + + 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_winsn; + s->insn_read = &ni_670x_ao_rinsn; + + 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, devpriv->mite->daq_io_addr + MISC_CONTROL_OFFSET); + /* Config of ao registers */ + writel(0x00, devpriv->mite->daq_io_addr + AO_CONTROL_OFFSET); + + return 0; +} + +static void ni_670x_detach(struct comedi_device *dev) +{ + struct ni_670x_private *devpriv = dev->private; + struct comedi_subdevice *s; + + if (dev->n_subdevices) { + s = &dev->subdevices[0]; + if (s) + kfree(s->range_table_list); + } + if (devpriv && devpriv->mite) { + mite_unsetup(devpriv->mite); + mite_free(devpriv->mite); + } + comedi_pci_disable(dev); +} + +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 00000000000..5bd19494dbf --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_at_a2150.c @@ -0,0 +1,821 @@ +/* + 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 "../comedidev.h" + +#include <linux/io.h> + +#include <asm/dma.h> + +#include "8253.h" +#include "comedi_fc.h" + +#define A2150_SIZE 28 +#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 +#define I8253_MODE_REG 0x17 +#define HW_COUNT_DISABLE 0x30 /* disable hardware counting of conversions */ + +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 { + + volatile unsigned int count; /* number of data points left to be taken */ + unsigned int dma; /* dma channel */ + uint16_t *dma_buffer; /* dma buffer */ + unsigned int dma_transfer_size; /* size in bytes of dma transfers */ + int irq_dma_bits; /* irq/dma register bits */ + int config_bits; /* config register bits */ +}; + +static int a2150_cancel(struct comedi_device *dev, struct comedi_subdevice *s); + +static int a2150_get_timing(struct comedi_device *dev, unsigned int *period, + int flags); +static int a2150_set_chanlist(struct comedi_device *dev, + unsigned int start_channel, + unsigned int num_channels); +/* interrupt service routine */ +static irqreturn_t a2150_interrupt(int irq, void *d) +{ + int i; + int status; + unsigned long flags; + struct comedi_device *dev = d; + struct a2150_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + unsigned int max_points, num_points, residue, leftover; + unsigned short dpnt; + static const int sample_size = sizeof(devpriv->dma_buffer[0]); + + if (!dev->attached) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + /* initialize async here to make sure s is not NULL */ + async = s->async; + cmd = &async->cmd; + + status = inw(dev->iobase + STATUS_REG); + + if ((status & INTR_BIT) == 0) { + comedi_error(dev, "spurious interrupt"); + return IRQ_NONE; + } + + if (status & OVFL_BIT) { + comedi_error(dev, "fifo overflow"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + } + + if ((status & DMA_TC_BIT) == 0) { + comedi_error(dev, "caught non-dma interrupt? Aborting."); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + return IRQ_HANDLED; + } + + flags = claim_dma_lock(); + disable_dma(devpriv->dma); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma); + + /* figure out how many points to read */ + max_points = devpriv->dma_transfer_size / sample_size; + /* residue is the number of points 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 = get_dma_residue(devpriv->dma) / sample_size; + num_points = max_points - 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 = devpriv->dma_transfer_size / sample_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 = devpriv->dma_buffer[i]; + /* convert from 2's complement to unsigned coding */ + dpnt ^= 0x8000; + cfc_write_to_buffer(s, dpnt); + if (cmd->stop_src == TRIG_COUNT) { + if (--devpriv->count == 0) { /* end of acquisition */ + async->events |= COMEDI_CB_EOA; + break; + } + } + } + /* re-enable dma */ + if (leftover) { + set_dma_addr(devpriv->dma, virt_to_bus(devpriv->dma_buffer)); + set_dma_count(devpriv->dma, leftover * sample_size); + enable_dma(devpriv->dma); + } + release_dma_lock(flags); + + async->events |= COMEDI_CB_BLOCK; + + cfc_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; + + /* 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 */ + disable_dma(devpriv->dma); + + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + 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 = comedi_board(dev); + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + thisboard->ai_speed); + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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 |= cfc_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_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned long timer_base = dev->iobase + I8253_BASE_REG; + unsigned long lock_flags; + unsigned int old_config_bits = devpriv->config_bits; + unsigned int trigger_bits; + + if (cmd->flags & TRIG_RT) { + comedi_error(dev, + " dma incompatible with hard real-time interrupt (TRIG_RT), aborting"); + 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; + + /* enable computer's dma */ + lock_flags = claim_dma_lock(); + disable_dma(devpriv->dma); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, virt_to_bus(devpriv->dma_buffer)); + /* set size of transfer to fill in 1/3 second */ +#define ONE_THIRD_SECOND 333333333 + devpriv->dma_transfer_size = + sizeof(devpriv->dma_buffer[0]) * cmd->chanlist_len * + ONE_THIRD_SECOND / cmd->scan_begin_arg; + if (devpriv->dma_transfer_size > A2150_DMA_BUFFER_SIZE) + devpriv->dma_transfer_size = A2150_DMA_BUFFER_SIZE; + if (devpriv->dma_transfer_size < sizeof(devpriv->dma_buffer[0])) + devpriv->dma_transfer_size = sizeof(devpriv->dma_buffer[0]); + devpriv->dma_transfer_size -= + devpriv->dma_transfer_size % sizeof(devpriv->dma_buffer[0]); + set_dma_count(devpriv->dma, devpriv->dma_transfer_size); + enable_dma(devpriv->dma); + release_dma_lock(lock_flags); + + /* 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 */ + i8254_set_mode(timer_base, 0, 2, I8254_MODE0 | I8254_BINARY); + i8254_write(timer_base, 0, 2, 72); + + /* 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 */ + comedi_error(dev, "you shouldn't see this?"); + } + /* 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; +} + +/* + * 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, + int flags) +{ + const struct a2150_board *thisboard = comedi_board(dev); + 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; + } + } + } + flags &= TRIG_ROUND_MASK; + switch (flags) { + case TRIG_ROUND_NEAREST: + default: + /* if least upper bound is better approximation */ + if (lub - *period < *period - glb) + *period = lub; + else + *period = glb; + break; + case TRIG_ROUND_UP: + *period = lub; + break; + case TRIG_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; + break; + } + + return 0; +} + +/* 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; + unsigned int irq = it->options[1]; + unsigned int dma = it->options[2]; + 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], A2150_SIZE); + if (ret) + return ret; + + dev->board_ptr = a2150_boards + a2150_probe(dev); + thisboard = comedi_board(dev); + dev->board_name = thisboard->name; + + if ((irq >= 3 && irq <= 7) || (irq >= 9 && irq <= 12) || + irq == 14 || irq == 15) { + ret = request_irq(irq, a2150_interrupt, 0, + dev->board_name, dev); + if (ret == 0) { + devpriv->irq_dma_bits |= IRQ_LVL_BITS(irq); + dev->irq = irq; + } + } + + if (dev->irq && dma <= 7 && dma != 4) { + ret = request_dma(dma, dev->board_name); + if (ret == 0) { + devpriv->dma = dma; + devpriv->dma_buffer = kmalloc(A2150_DMA_BUFFER_SIZE, + GFP_KERNEL | GFP_DMA); + if (!devpriv->dma_buffer) + return -ENOMEM; + + disable_dma(dma); + set_dma_mode(dma, DMA_MODE_READ); + + devpriv->irq_dma_bits |= DMA_CHAN_BITS(dma); + } + } + + 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 && devpriv->dma) { + 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; + } + + /* need to do this for software counting of completed conversions, to + * prevent hardware count from stopping acquisition */ + outw(HW_COUNT_DISABLE, dev->iobase + I8253_MODE_REG); + + /* 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) { + printk + (" 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) +{ + struct a2150_private *devpriv = dev->private; + + if (dev->iobase) + outw(APD_BIT | DPD_BIT, dev->iobase + CONFIG_REG); + if (devpriv) { + if (devpriv->dma) + free_dma(devpriv->dma); + kfree(devpriv->dma_buffer); + } + 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 00000000000..c93b47bcca5 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_at_ao.c @@ -0,0 +1,409 @@ +/* + * 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] + * (National Instruments) 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 "8253.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 AO readback */ + unsigned int ao_readback[10]; + + /* 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) +{ + struct atao_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int i; + + if (chan == 0) + atao_select_reg_group(dev, 1); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + devpriv->ao_readback[chan] = val; + + /* munge offset binary (unsigned) to two's complement */ + val = comedi_offset_munge(s, val); + outw(val, dev->iobase + ATAO_AO_REG(chan)); + } + + if (chan == 0) + atao_select_reg_group(dev, 0); + + return insn->n; +} + +static int atao_ao_insn_read(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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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) +{ + struct atao_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int bitstring; + unsigned int val; + int bit; + + if (insn->n == 0) + return 0; + + devpriv->caldac[chan] = data[insn->n - 1] & s->maxdata; + + /* write the channel and last data value to the caldac */ + bitstring = ((chan & 0x7) << 8) | devpriv->caldac[chan]; + + /* clock the bitstring to the caldac; MSB -> LSB */ + for (bit = 1 << 10; bit; bit >>= 1) { + val = (bit & bitstring) ? ATAO_CFG2_SDATA : 0; + + outw(val, dev->iobase + ATAO_CFG2_REG); + outw(val | 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); + + return insn->n; +} + +static int atao_calib_insn_read(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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->caldac[chan]; + + return insn->n; +} + +static void atao_reset(struct comedi_device *dev) +{ + struct atao_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + ATAO_82C53_BASE; + + /* 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 */ + i8254_set_mode(timer_base, 0, 0, I8254_MODE4 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 1, I8254_MODE4 | I8254_BINARY); + i8254_write(timer_base, 0, 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 = comedi_board(dev); + 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; + + 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; + s->insn_read = atao_ao_insn_read; + + /* 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_read = atao_calib_insn_read; + s->insn_write = atao_calib_insn_write; + + /* 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 00000000000..d03935257b9 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_atmio.c @@ -0,0 +1,494 @@ +/* + 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" + +#define ATMIO 1 +#undef PCIMIO + +/* + * AT specific setup + */ + +#define NI_SIZE 0x20 + +#define MAX_N_CALDACS 32 + +static const struct ni_board_struct ni_boards[] = { + {.device_id = 44, + .isapnp_id = 0x0000, /* XXX unknown */ + .name = "at-mio-16e-1", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .has_8255 = 0, + .num_p0_dio_channels = 8, + .caldac = {mb88341}, + }, + {.device_id = 25, + .isapnp_id = 0x1900, + .name = "at-mio-16e-2", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 2048, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .has_8255 = 0, + .num_p0_dio_channels = 8, + .caldac = {mb88341}, + }, + {.device_id = 36, + .isapnp_id = 0x2400, + .name = "at-mio-16e-10", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + {.device_id = 37, + .isapnp_id = 0x2500, + .name = "at-mio-16de-10", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 1, + }, + {.device_id = 38, + .isapnp_id = 0x2600, + .name = "at-mio-64e-3", + .n_adchan = 64, + .adbits = 12, + .ai_fifo_depth = 2048, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .has_8255 = 0, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + }, + {.device_id = 39, + .isapnp_id = 0x2700, + .name = "at-mio-16xe-50", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_8, + .ai_speed = 50000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 50000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043}, + .has_8255 = 0, + }, + {.device_id = 50, + .isapnp_id = 0x0000, /* XXX unknown */ + .name = "at-mio-16xe-10", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + }, + {.device_id = 51, + .isapnp_id = 0x0000, /* XXX unknown */ + .name = "at-ai-16xe-10", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, /* unknown */ + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + } +}; + +static const int ni_irqpin[] = { + -1, -1, -1, 0, 1, 2, -1, 3, -1, -1, 4, 5, 6, -1, -1, 7 +}; + +#define interrupt_pin(a) (ni_irqpin[(a)]) + +#define IRQ_POLARITY 0 + +#define NI_E_IRQ_FLAGS 0 + +struct ni_private { + struct pnp_dev *isapnp_dev; + NI_PRIVATE_COMMON + +}; + +/* How we access registers */ + +#define ni_writel(a, b) (outl((a), (b)+dev->iobase)) +#define ni_readl(a) (inl((a)+dev->iobase)) +#define ni_writew(a, b) (outw((a), (b)+dev->iobase)) +#define ni_readw(a) (inw((a)+dev->iobase)) +#define ni_writeb(a, b) (outb((a), (b)+dev->iobase)) +#define ni_readb(a) (inb((a)+dev->iobase)) + +/* How we access windowed registers */ + +/* We automatically take advantage of STC registers that can be + * read/written directly in the I/O space of the board. The + * AT-MIO devices map the low 8 STC registers to iobase+addr*2. */ + +static void ni_atmio_win_out(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); + if ((addr) < 8) { + ni_writew(data, addr * 2); + } else { + ni_writew(addr, Window_Address); + ni_writew(data, Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static uint16_t ni_atmio_win_in(struct comedi_device *dev, int addr) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + uint16_t ret; + + spin_lock_irqsave(&devpriv->window_lock, flags); + if (addr < 8) { + ret = ni_readw(addr * 2); + } else { + ni_writew(addr, Window_Address); + ret = ni_readw(Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); + + return ret; +} + +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); + +#include "ni_mio_common.c" + +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 == NULL || isapnp_dev->card == NULL) + continue; + + if (pnp_device_attach(isapnp_dev) < 0) { + printk + ("ni_atmio: %s found but already active, skipping.\n", + ni_boards[i].name); + 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); + printk("ni_atmio: pnp invalid port or irq, aborting\n"); + 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) + printk(" can't find board\n"); + else if (device_id == 0) + printk(" EEPROM read error (?) or device not found\n"); + else + printk(" 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 ni_private *devpriv; + struct pnp_dev *isapnp_dev; + int ret; + unsigned long iobase; + int board; + unsigned int irq; + + ret = ni_alloc_private(dev); + if (ret) + return ret; + devpriv = dev->private; + + devpriv->stc_writew = &ni_atmio_win_out; + devpriv->stc_readw = &ni_atmio_win_in; + devpriv->stc_writel = &win_out2; + devpriv->stc_readl = &win_in2; + + 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); + devpriv->isapnp_dev = isapnp_dev; + } + + ret = comedi_request_region(dev, iobase, NI_SIZE); + if (ret) + return ret; + + /* get board type */ + + board = ni_getboardtype(dev); + if (board < 0) + return -EIO; + + dev->board_ptr = ni_boards + board; + boardtype = comedi_board(dev); + + printk(" %s", boardtype->name); + dev->board_name = boardtype->name; + + /* irq stuff */ + + if (irq != 0) { + if (irq > 15 || ni_irqpin[irq] == -1) { + printk(" invalid irq %u\n", irq); + return -EINVAL; + } + printk(" ( irq = %u )", irq); + ret = request_irq(irq, ni_E_interrupt, NI_E_IRQ_FLAGS, + "ni_atmio", dev); + + if (ret < 0) { + printk(" irq not available\n"); + return -EINVAL; + } + dev->irq = irq; + } + + /* generic E series stuff in ni_mio_common.c */ + + ret = ni_E_init(dev); + if (ret < 0) + return ret; + + + return 0; +} + +static void ni_atmio_detach(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + + mio_common_detach(dev); + comedi_legacy_detach(dev); + if (devpriv->isapnp_dev) + pnp_device_detach(devpriv->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 00000000000..6ad27f50c6e --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_atmio16d.c @@ -0,0 +1,781 @@ +/* + 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 "comedi_fc.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 +/* Other miscellaneous defines */ +#define ATMIO16D_SIZE 32 /* bus address range */ + +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 ao_readback[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; + + comedi_buf_put(s, inw(dev->iobase + AD_FIFO_REG)); + + comedi_event(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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } else { +#if 0 + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); +#endif + } + + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000); +#if 0 + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER); +#endif + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { /* TRIG_NONE */ + err |= cfc_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); + s->async->cur_chan = 0; + + /* 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 if (cmd->convert_arg <= 0xffffffff /* 6553600000 */) { + base_clock = CLOCK_10_KHZ; + timer = cmd->convert_arg / 100000; + } else if (cmd->convert_arg <= 0xffffffff /* 65536000000 */) { + base_clock = CLOCK_1_KHZ; + timer = cmd->convert_arg / 1000000; + } + 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 if (cmd->scan_begin_arg < 0xffffffff /* 6553600000 */) { + base_clock = CLOCK_10_KHZ; + timer = cmd->scan_begin_arg / 100000; + } else if (cmd->scan_begin_arg < 0xffffffff /* 65536000000 */) { + base_clock = CLOCK_1_KHZ; + timer = cmd->scan_begin_arg / 1000000; + } + 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_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct atmio16d_private *devpriv = dev->private; + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + 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; + int i; + int chan; + int d; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + d = data[i]; + switch (chan) { + case 0: + if (devpriv->dac0_coding == dac_2comp) + d ^= 0x800; + outw(d, dev->iobase + DAC0_REG); + break; + case 1: + if (devpriv->dac1_coding == dac_2comp) + d ^= 0x800; + outw(d, dev->iobase + DAC1_REG); + break; + default: + return -EINVAL; + } + devpriv->ao_readback[chan] = data[i]; + } + return i; +} + +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 = comedi_board(dev); + struct atmio16d_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], ATMIO16D_SIZE); + 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->insn_read = atmio16d_ao_insn_read; + s->insn_write = atmio16d_ao_insn_write; + 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; + } + + /* 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, dev->iobase); + 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 00000000000..728bf7f14f7 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_daq_700.c @@ -0,0 +1,267 @@ +/* + * 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 DIO only +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, 19 Sep 2012 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. + +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 (8225 device +emu as port A output, port B input, port C N/A). + +Analog: The input range is 0 to 4095 for -10 to +10 volts +IRQ is assigned but not used. + +Version 0.1 Original DIO only driver +Version 0.2 DIO and basic AI analog input support on 16 se channels + +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 "../comedidev.h" + +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.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 */ + +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, chan; + int d; + int ret; + + chan = CR_CHAN(insn->chanspec); + /* 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 */ + /* 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; + /* we support single-ended (ground) */ + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 16; + s->maxdata = (1 << 12) - 1; + s->range_table = &range_bipolar10; + 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_VERSION("0.2.00"); +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 00000000000..925e82c65b2 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_daq_dio24.c @@ -0,0 +1,99 @@ +/* + 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 "../comedidev.h" + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.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, dev->iobase); + 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 00000000000..3e3f940fa57 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc.c @@ -0,0 +1,1515 @@ +/* + * 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] + * (National Instruments) Lab-PC-1200AI [lab-pc-1200ai] + * (National Instruments) 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 <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/delay.h> + +#include "../comedidev.h" + +#include "8253.h" +#include "8255.h" +#include "comedi_fc.h" +#include "ni_labpc.h" +#include "ni_labpc_regs.h" +#include "ni_labpc_isadma.h" + +#define LABPC_SIZE 0x20 /* size of ISA io region */ + +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 inline unsigned int labpc_inb(unsigned long address) +{ + return inb(address); +} + +static inline void labpc_outb(unsigned int byte, unsigned long address) +{ + outb(byte, address); +} + +static inline unsigned int labpc_readb(unsigned long address) +{ + return readb((void __iomem *)address); +} + +static inline void labpc_writeb(unsigned int byte, unsigned long address) +{ + writeb(byte, (void __iomem *)address); +} + +#if IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISA) +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, + }, +}; +#endif + +static void labpc_counter_load(struct comedi_device *dev, + unsigned long base_address, + unsigned int counter_number, + unsigned int count, + unsigned int mode) +{ + const struct labpc_boardinfo *board = comedi_board(dev); + + if (board->has_mmio) { + void __iomem *mmio_base = (void __iomem *)base_address; + + i8254_mm_set_mode(mmio_base, 0, counter_number, mode); + i8254_mm_write(mmio_base, 0, counter_number, count); + } else { + i8254_set_mode(base_address, 0, counter_number, mode); + i8254_write(base_address, 0, counter_number, count); + } +} + +static void labpc_counter_set_mode(struct comedi_device *dev, + unsigned long base_address, + unsigned int counter_number, + unsigned int mode) +{ + const struct labpc_boardinfo *board = comedi_board(dev); + + if (board->has_mmio) { + void __iomem *mmio_base = (void __iomem *)base_address; + + i8254_mm_set_mode(mmio_base, 0, counter_number, mode); + } else { + i8254_set_mode(base_address, 0, counter_number, mode); + } +} + +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(devpriv->cmd2, dev->iobase + CMD2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + devpriv->cmd3 = 0; + devpriv->write_byte(devpriv->cmd3, dev->iobase + 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 = comedi_board(dev); + 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(devpriv->cmd1, dev->iobase + 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 = comedi_board(dev); + 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(devpriv->cmd6, dev->iobase + 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->iobase + ADC_FIFO_REG); + unsigned int msb = devpriv->read_byte(dev->iobase + 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(0x1, dev->iobase + 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->iobase + 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(devpriv->cmd4, dev->iobase + CMD4_REG); + + /* initialize pacer counter to prevent any problems */ + labpc_counter_set_mode(dev, dev->iobase + COUNTER_A_BASE_REG, + 0, I8254_MODE2); + + labpc_clear_adc_fifo(dev); + + for (i = 0; i < insn->n; i++) { + /* trigger conversion */ + devpriv->write_byte(0x1, dev->iobase + 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 labpc_private *devpriv = dev->private; + /* max value for 16 bit counter in mode 2 */ + const int max_counter_value = 0x10000; + /* min value for 16 bit counter in mode 2 */ + const int min_counter_value = 2; + unsigned int base_period; + unsigned int scan_period; + unsigned int convert_period; + + /* + * if both convert and scan triggers are TRIG_TIMER, then they + * both rely on counter b0 + */ + convert_period = labpc_ai_convert_period(cmd, mode); + scan_period = labpc_ai_scan_period(cmd, mode); + if (convert_period && scan_period) { + /* + * pick the lowest b0 divisor value we can (for maximum input + * clock speed on convert and scan counters) + */ + devpriv->divisor_b0 = (scan_period - 1) / + (I8254_OSC_BASE_2MHZ * max_counter_value) + 1; + if (devpriv->divisor_b0 < min_counter_value) + devpriv->divisor_b0 = min_counter_value; + if (devpriv->divisor_b0 > max_counter_value) + devpriv->divisor_b0 = max_counter_value; + + base_period = I8254_OSC_BASE_2MHZ * devpriv->divisor_b0; + + /* set a0 for conversion frequency and b1 for scan frequency */ + switch (cmd->flags & TRIG_ROUND_MASK) { + default: + case TRIG_ROUND_NEAREST: + devpriv->divisor_a0 = + (convert_period + (base_period / 2)) / base_period; + devpriv->divisor_b1 = + (scan_period + (base_period / 2)) / base_period; + break; + case TRIG_ROUND_UP: + devpriv->divisor_a0 = + (convert_period + (base_period - 1)) / base_period; + devpriv->divisor_b1 = + (scan_period + (base_period - 1)) / base_period; + break; + case TRIG_ROUND_DOWN: + devpriv->divisor_a0 = convert_period / base_period; + devpriv->divisor_b1 = scan_period / base_period; + break; + } + /* make sure a0 and b1 values are acceptable */ + if (devpriv->divisor_a0 < min_counter_value) + devpriv->divisor_a0 = min_counter_value; + if (devpriv->divisor_a0 > max_counter_value) + devpriv->divisor_a0 = max_counter_value; + if (devpriv->divisor_b1 < min_counter_value) + devpriv->divisor_b1 = min_counter_value; + if (devpriv->divisor_b1 > max_counter_value) + devpriv->divisor_b1 = max_counter_value; + /* write corrected timings to command */ + labpc_set_ai_convert_period(cmd, mode, + base_period * devpriv->divisor_a0); + labpc_set_ai_scan_period(cmd, mode, + base_period * devpriv->divisor_b1); + /* + * if only one TRIG_TIMER is used, we can employ the generic + * cascaded timing functions + */ + } else if (scan_period) { + /* + * calculate cascaded counter values + * that give desired scan timing + */ + i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ, + &devpriv->divisor_b1, + &devpriv->divisor_b0, + &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 + */ + i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ, + &devpriv->divisor_a0, + &devpriv->divisor_b0, + &convert_period, cmd->flags); + labpc_set_ai_convert_period(cmd, mode, convert_period); + } +} + +static enum scan_mode labpc_ai_scan_mode(const struct comedi_cmd *cmd) +{ + if (cmd->chanlist_len == 1) + return MODE_SINGLE_CHAN; + + /* chanlist may be NULL during cmdtest. */ + if (cmd->chanlist == NULL) + return MODE_MULT_CHAN_UP; + + if (CR_CHAN(cmd->chanlist[0]) == CR_CHAN(cmd->chanlist[1])) + return MODE_SINGLE_CHAN_INTERVAL; + + if (CR_CHAN(cmd->chanlist[0]) < CR_CHAN(cmd->chanlist[1])) + return MODE_MULT_CHAN_UP; + + if (CR_CHAN(cmd->chanlist[0]) > CR_CHAN(cmd->chanlist[1])) + return MODE_MULT_CHAN_DOWN; + + pr_err("ni_labpc: bug! cannot determine AI scan mode\n"); + return 0; +} + +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; + + if (mode == MODE_SINGLE_CHAN) + return 0; + + 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 = comedi_board(dev); + int err = 0; + int tmp, tmp2; + unsigned int stop_mask; + enum scan_mode mode; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + + stop_mask = TRIG_COUNT | TRIG_NONE; + if (board->is_labpc1200) + stop_mask |= TRIG_EXT; + err |= cfc_check_trigger_src(&cmd->stop_src, stop_mask); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_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 |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + cmd->convert_arg * cmd->chanlist_len); + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ai_speed * cmd->chanlist_len); + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_NONE: + err |= cfc_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 = comedi_board(dev); + 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 + */ + labpc_counter_load(dev, dev->iobase + COUNTER_A_BASE_REG, + 1, 3, I8254_MODE0); + } else { + /* just put counter a1 in mode 0 to set its output low */ + labpc_counter_set_mode(dev, dev->iobase + COUNTER_A_BASE_REG, + 1, I8254_MODE0); + } + + /* figure out what method we will use to transfer data */ + if (labpc_have_dma_chan(dev) && + /* dma unsafe at RT priority, + * and too much setup time for TRIG_WAKE_EOS */ + (cmd->flags & (TRIG_WAKE_EOS | TRIG_RT)) == 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 & TRIG_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(devpriv->cmd1, dev->iobase + CMD1_REG); + } + + devpriv->write_byte(cmd->chanlist_len, + dev->iobase + INTERVAL_COUNT_REG); + /* load count */ + devpriv->write_byte(0x1, dev->iobase + INTERVAL_STROBE_REG); + + if (cmd->convert_src == TRIG_TIMER || + cmd->scan_begin_src == TRIG_TIMER) { + /* set up pacing */ + labpc_adc_timing(dev, cmd, mode); + /* load counter b0 in mode 3 */ + labpc_counter_load(dev, dev->iobase + COUNTER_B_BASE_REG, + 0, devpriv->divisor_b0, I8254_MODE3); + } + /* set up conversion pacing */ + if (labpc_ai_convert_period(cmd, mode)) { + /* load counter a0 in mode 2 */ + labpc_counter_load(dev, dev->iobase + COUNTER_A_BASE_REG, + 0, devpriv->divisor_a0, I8254_MODE2); + } else { + /* initialize pacer counter to prevent any problems */ + labpc_counter_set_mode(dev, dev->iobase + COUNTER_A_BASE_REG, + 0, I8254_MODE2); + } + + /* set up scan pacing */ + if (labpc_ai_scan_period(cmd, mode)) { + /* load counter b1 in mode 2 */ + labpc_counter_load(dev, dev->iobase + COUNTER_B_BASE_REG, + 1, devpriv->divisor_b1, I8254_MODE2); + } + + 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(devpriv->cmd3, dev->iobase + 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(devpriv->cmd4, dev->iobase + 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(devpriv->cmd2, dev->iobase + 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->iobase + 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); + cfc_write_to_buffer(dev->read_subdev, data); + devpriv->stat1 = devpriv->read_byte(dev->iobase + STAT1_REG); + } + if (i == timeout) { + comedi_error(dev, "ai timeout, fifo never empties"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + 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 = comedi_board(dev); + struct labpc_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + + if (!dev->attached) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + + async = s->async; + cmd = &async->cmd; + + /* read board status */ + devpriv->stat1 = devpriv->read_byte(dev->iobase + STAT1_REG); + if (board->is_labpc1200) + devpriv->stat2 = devpriv->read_byte(dev->iobase + 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(0x1, dev->iobase + ADC_FIFO_CLEAR_REG); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + comedi_error(dev, "overrun"); + 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) { + comedi_error(dev, "handled timer interrupt?"); + /* clear it */ + devpriv->write_byte(0x1, dev->iobase + TIMER_CLEAR_REG); + } + + if (devpriv->stat1 & STAT1_OVERFLOW) { + /* clear error interrupt */ + devpriv->write_byte(0x1, dev->iobase + ADC_FIFO_CLEAR_REG); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + cfc_handle_events(dev, s); + comedi_error(dev, "overflow"); + 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; + } + + cfc_handle_events(dev, s); + return IRQ_HANDLED; +} + +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 = comedi_board(dev); + struct labpc_private *devpriv = dev->private; + int channel, range; + unsigned long flags; + int lsb, msb; + + 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(devpriv->cmd2, dev->iobase + 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(devpriv->cmd6, dev->iobase + CMD6_REG); + } + /* send data */ + lsb = data[0] & 0xff; + msb = (data[0] >> 8) & 0xff; + devpriv->write_byte(lsb, dev->iobase + DAC_LSB_REG(channel)); + devpriv->write_byte(msb, dev->iobase + DAC_MSB_REG(channel)); + + /* remember value for readback */ + devpriv->ao_value[channel] = data[0]; + + return 1; +} + +static int labpc_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct labpc_private *devpriv = dev->private; + + data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int labpc_8255_mmio(int dir, int port, int data, unsigned long iobase) +{ + if (dir) { + writeb(data, (void __iomem *)(iobase + port)); + return 0; + } else { + return readb((void __iomem *)(iobase + port)); + } +} + +/* 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(devpriv->cmd5, dev->iobase + CMD5_REG); + /* set clock to load bit */ + devpriv->cmd5 |= CMD5_SCLK; + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + 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(devpriv->cmd5, dev->iobase + CMD5_REG); + /* clear clock bit */ + devpriv->cmd5 &= ~CMD5_SCLK; + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG); + /* read bits most significant bit first */ + udelay(1); + devpriv->stat2 = devpriv->read_byte(dev->iobase + 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(devpriv->cmd5, dev->iobase + CMD5_REG); + devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + 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(devpriv->cmd5, dev->iobase + 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(devpriv->cmd5, dev->iobase + CMD5_REG); + devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + 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(devpriv->cmd5, dev->iobase + CMD5_REG); + + return value; +} + +static int 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 */ + const int write_in_progress_bit = 0x1; + const int timeout = 10000; + int i; + + /* make sure there isn't already a write in progress */ + for (i = 0; i < timeout; i++) { + if ((labpc_eeprom_read_status(dev) & write_in_progress_bit) == + 0) + break; + } + if (i == timeout) { + comedi_error(dev, "eeprom write timed out"); + return -ETIME; + } + /* update software copy of eeprom */ + devpriv->eeprom_data[address] = value; + + /* enable read/write to eeprom */ + devpriv->cmd5 &= ~CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG); + devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG); + + /* send write_enable instruction */ + labpc_serial_out(dev, write_enable_instruction, write_length); + devpriv->cmd5 &= ~CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG); + + /* send write instruction */ + devpriv->cmd5 |= CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + 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(devpriv->cmd5, dev->iobase + CMD5_REG); + + /* disable read/write to eeprom */ + devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG); + + return 0; +} + +/* 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; + + if (value == devpriv->caldac[channel]) + return; + devpriv->caldac[channel] = value; + + /* 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(devpriv->cmd5, dev->iobase + 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(devpriv->cmd5, dev->iobase + CMD5_REG); + devpriv->cmd5 &= ~CMD5_CALDACLD; + udelay(1); + devpriv->write_byte(devpriv->cmd5, dev->iobase + 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) + write_caldac(dev, chan, data[insn->n - 1]); + + return insn->n; +} + +static int labpc_calib_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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->caldac[chan]; + + return insn->n; +} + +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) { + ret = labpc_eeprom_write(dev, chan, data[insn->n - 1]); + if (ret) + return ret; + } + + return insn->n; +} + +static int labpc_eeprom_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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->eeprom_data[chan]; + + return insn->n; +} + +int labpc_common_attach(struct comedi_device *dev, + unsigned int irq, unsigned long isr_flags) +{ + const struct labpc_boardinfo *board = comedi_board(dev); + struct labpc_private *devpriv = dev->private; + struct comedi_subdevice *s; + int ret; + int i; + + if (board->has_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(devpriv->cmd1, dev->iobase + CMD1_REG); + devpriv->write_byte(devpriv->cmd2, dev->iobase + CMD2_REG); + devpriv->write_byte(devpriv->cmd3, dev->iobase + CMD3_REG); + devpriv->write_byte(devpriv->cmd4, dev->iobase + CMD4_REG); + if (board->is_labpc1200) { + devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG); + devpriv->write_byte(devpriv->cmd6, dev->iobase + CMD6_REG); + } + + if (irq) { + ret = request_irq(irq, labpc_interrupt, isr_flags, + dev->board_name, dev); + if (ret == 0) + dev->irq = irq; + } + + 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_read = labpc_ao_insn_read; + s->insn_write = labpc_ao_insn_write; + + /* initialize analog outputs to a known value */ + for (i = 0; i < s->n_chan; i++) { + short lsb, msb; + + devpriv->ao_value[i] = s->maxdata / 2; + lsb = devpriv->ao_value[i] & 0xff; + msb = (devpriv->ao_value[i] >> 8) & 0xff; + devpriv->write_byte(lsb, dev->iobase + DAC_LSB_REG(i)); + devpriv->write_byte(msb, dev->iobase + DAC_MSB_REG(i)); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8255 dio */ + s = &dev->subdevices[2]; + ret = subdev_8255_init(dev, s, + (board->has_mmio) ? labpc_8255_mmio : NULL, + dev->iobase + 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_read = labpc_calib_insn_read; + s->insn_write = labpc_calib_insn_write; + + for (i = 0; i < s->n_chan; i++) + write_caldac(dev, 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_read = labpc_eeprom_insn_read; + s->insn_write = labpc_eeprom_insn_write; + + for (i = 0; i < s->n_chan; i++) + devpriv->eeprom_data[i] = labpc_eeprom_read(dev, i); + } else + s->type = COMEDI_SUBD_UNUSED; + + return 0; +} +EXPORT_SYMBOL_GPL(labpc_common_attach); + +#if IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISA) +static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct labpc_private *devpriv; + unsigned int irq = it->options[1]; + unsigned int dma_chan = it->options[2]; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], LABPC_SIZE); + 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) +{ + struct labpc_private *devpriv = dev->private; + + if (devpriv) + labpc_free_dma_chan(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); +#else +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); +#endif + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +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 00000000000..486589fa6fd --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc.h @@ -0,0 +1,90 @@ +/* + 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+ */ + unsigned has_mmio:1; /* uses memory mapped io */ +}; + +struct labpc_private { + struct mite_struct *mite; /* for mite chip on pci-1200 */ + /* number of data points left to be taken */ + unsigned long long count; + /* software copy of analog output values */ + unsigned int ao_value[NUM_AO_CHAN]; + /* 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; + /* + * value to load into board's counter a0 (conversion pacing) for timed + * conversions + */ + unsigned int divisor_a0; + /* + * value to load into board's counter b0 (master) for timed conversions + */ + unsigned int divisor_b0; + /* + * value to load into board's counter b1 (scan pacing) for timed + * conversions + */ + unsigned int divisor_b1; + unsigned int dma_chan; /* dma channel to use */ + u16 *dma_buffer; /* buffer ai will dma into */ + phys_addr_t dma_addr; + /* transfer size in bytes for current transfer */ + unsigned int dma_transfer_size; + /* we are using dma/fifo-half-full/etc. */ + enum transfer_type current_transfer; + /* stores contents of board's eeprom */ + unsigned int eeprom_data[EEPROM_SIZE]; + /* stores settings of calibration dacs */ + unsigned int caldac[16]; + /* + * function pointers so we can use inb/outb or readb/writeb as + * appropriate + */ + unsigned int (*read_byte) (unsigned long address); + void (*write_byte) (unsigned int byte, unsigned long address); +}; + +int labpc_common_attach(struct comedi_device *dev, + unsigned int irq, unsigned long isr_flags); + +#endif /* _NI_LABPC_H */ 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 00000000000..0a8b3223f74 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc_cs.c @@ -0,0 +1,135 @@ +/* + 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 "../comedidev.h" + +#include <linux/delay.h> + +#include "8253.h" +#include "8255.h" +#include "comedi_fc.h" +#include "ni_labpc.h" + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> + +static const struct labpc_boardinfo labpc_cs_boards[] = { + { + .name = "daqcard-1200", + .ai_speed = 10000, + .has_ao = 1, + .is_labpc1200 = 1, + }, +}; + +static int labpc_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + struct labpc_private *devpriv; + 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; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + return labpc_common_attach(dev, link->irq, IRQF_SHARED); +} + +static struct comedi_driver driver_labpc_cs = { + .driver_name = "ni_labpc_cs", + .module = THIS_MODULE, + .auto_attach = labpc_auto_attach, + .detach = comedi_pcmcia_disable, +}; + +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 00000000000..d9f25fdbb72 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc_isadma.c @@ -0,0 +1,227 @@ +/* + * 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 <asm/dma.h> + +#include "comedi_fc.h" +#include "ni_labpc.h" +#include "ni_labpc_regs.h" +#include "ni_labpc_isadma.h" + +/* size in bytes of dma buffer */ +static const int dma_buffer_size = 0xff00; +/* 2 bytes per sample */ +static const int sample_size = 2; + +/* utility function that suggests a dma transfer size in bytes */ +static unsigned int labpc_suggest_transfer_size(const struct comedi_cmd *cmd) +{ + 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 > dma_buffer_size) + size = dma_buffer_size - dma_buffer_size % sample_size; + 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_cmd *cmd = &s->async->cmd; + unsigned long irq_flags; + + irq_flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma_chan); + set_dma_addr(devpriv->dma_chan, devpriv->dma_addr); + /* set appropriate size of transfer */ + devpriv->dma_transfer_size = labpc_suggest_transfer_size(cmd); + if (cmd->stop_src == TRIG_COUNT && + devpriv->count * sample_size < devpriv->dma_transfer_size) + devpriv->dma_transfer_size = devpriv->count * sample_size; + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + release_dma_lock(irq_flags); + /* 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_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int status; + unsigned long flags; + unsigned int max_points, num_points, residue, leftover; + int i; + + status = devpriv->stat1; + + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma_chan); + + /* figure out how many points to read */ + max_points = devpriv->dma_transfer_size / sample_size; + /* residue is the number of points 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 = get_dma_residue(devpriv->dma_chan) / sample_size; + num_points = max_points - residue; + if (cmd->stop_src == TRIG_COUNT && devpriv->count < num_points) + num_points = devpriv->count; + + /* figure out how many points will be stored next time */ + leftover = 0; + if (cmd->stop_src != TRIG_COUNT) { + leftover = devpriv->dma_transfer_size / sample_size; + } else if (devpriv->count > num_points) { + leftover = devpriv->count - num_points; + if (leftover > max_points) + leftover = max_points; + } + + /* write data to comedi buffer */ + for (i = 0; i < num_points; i++) + cfc_write_to_buffer(s, devpriv->dma_buffer[i]); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->count -= num_points; + + /* set address and count for next transfer */ + set_dma_addr(devpriv->dma_chan, devpriv->dma_addr); + set_dma_count(devpriv->dma_chan, leftover * sample_size); + release_dma_lock(flags); + + async->events |= COMEDI_CB_BLOCK; +} +EXPORT_SYMBOL_GPL(labpc_drain_dma); + +static void handle_isa_dma(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + labpc_drain_dma(dev); + + enable_dma(devpriv->dma_chan); + + /* clear dma tc interrupt */ + devpriv->write_byte(0x1, dev->iobase + DMATC_CLEAR_REG); +} + +void labpc_handle_dma_status(struct comedi_device *dev) +{ + const struct labpc_boardinfo *board = comedi_board(dev); + 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); + +int labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan) +{ + struct labpc_private *devpriv = dev->private; + void *dma_buffer; + unsigned long dma_flags; + int ret; + + if (dma_chan != 1 && dma_chan != 3) + return -EINVAL; + + dma_buffer = kmalloc(dma_buffer_size, GFP_KERNEL | GFP_DMA); + if (!dma_buffer) + return -ENOMEM; + + ret = request_dma(dma_chan, dev->board_name); + if (ret) { + kfree(dma_buffer); + return ret; + } + + devpriv->dma_buffer = dma_buffer; + devpriv->dma_chan = dma_chan; + devpriv->dma_addr = virt_to_bus(devpriv->dma_buffer); + + dma_flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + set_dma_mode(devpriv->dma_chan, DMA_MODE_READ); + release_dma_lock(dma_flags); + + return 0; +} +EXPORT_SYMBOL_GPL(labpc_init_dma_chan); + +void labpc_free_dma_chan(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + kfree(devpriv->dma_buffer); + devpriv->dma_buffer = NULL; + if (devpriv->dma_chan) { + free_dma(devpriv->dma_chan); + devpriv->dma_chan = 0; + } +} +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 00000000000..771af4bd5a7 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc_isadma.h @@ -0,0 +1,57 @@ +/* + * ni_labpc ISA DMA support. +*/ + +#ifndef _NI_LABPC_ISADMA_H +#define _NI_LABPC_ISADMA_H + +#define NI_LABPC_HAVE_ISA_DMA IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISADMA) + +#if NI_LABPC_HAVE_ISA_DMA + +static inline bool labpc_have_dma_chan(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + return (bool)devpriv->dma_chan; +} + +int 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 bool labpc_have_dma_chan(struct comedi_device *dev) +{ + return false; +} + +static inline int labpc_init_dma_chan(struct comedi_device *dev, + unsigned int dma_chan) +{ + return -ENOTSUPP; +} + +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 00000000000..73959706829 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc_pci.c @@ -0,0 +1,133 @@ +/* + * 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 <linux/pci.h> + +#include "../comedidev.h" + +#include "mite.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, + .has_mmio = 1, + }, +}; + +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; + struct labpc_private *devpriv; + 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; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->mite = mite_alloc(pcidev); + if (!devpriv->mite) + return -ENOMEM; + ret = mite_setup(devpriv->mite); + if (ret < 0) + return ret; + dev->iobase = (unsigned long)devpriv->mite->daq_io_addr; + + return labpc_common_attach(dev, mite_irq(devpriv->mite), IRQF_SHARED); +} + +static void labpc_pci_detach(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + if (devpriv && devpriv->mite) { + mite_unsetup(devpriv->mite); + mite_free(devpriv->mite); + } + if (dev->irq) + free_irq(dev->irq, dev); + comedi_pci_disable(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 00000000000..2a274a3e4e7 --- /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 00000000000..7ffdcc07ef9 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_mio_common.c @@ -0,0 +1,5740 @@ +/* + 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" +#include "comedi_fc.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 +}; + +static int ni_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_cdio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int ni_cdio_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_cdio_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void handle_cdio_interrupt(struct comedi_device *dev); +static int ni_cdo_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum); + +static int ni_serial_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_serial_hw_readwrite8(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in); +static int ni_serial_sw_readwrite8(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in); + +static int ni_calib_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int ni_eeprom_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_m_series_eeprom_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int ni_pfi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_pfi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static unsigned ni_old_get_pfi_routing(struct comedi_device *dev, + unsigned chan); + +static void ni_rtsi_init(struct comedi_device *dev); +static int ni_rtsi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_rtsi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static void caldac_setup(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_read_eeprom(struct comedi_device *dev, int addr); + +#ifndef PCIDMA +static void ni_handle_fifo_half_full(struct comedi_device *dev); +static int ni_ao_fifo_half_empty(struct comedi_device *dev, + struct comedi_subdevice *s); +#endif +static void ni_handle_fifo_dregs(struct comedi_device *dev); +static int ni_ai_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum); +static void ni_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, unsigned int *list); +static void shutdown_ai_command(struct comedi_device *dev); + +static int ni_ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum); + +static int ni_8255_callback(int dir, int port, int data, unsigned long arg); + +#ifdef PCIDMA +static int ni_gpct_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_gpct_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +#endif +static void handle_gpct_interrupt(struct comedi_device *dev, + unsigned short counter_index); + +static int init_cs5529(struct comedi_device *dev); +static int cs5529_do_conversion(struct comedi_device *dev, + unsigned short *data); +static int cs5529_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static void cs5529_config_write(struct comedi_device *dev, unsigned int value, + unsigned int reg_select_bits); + +static int ni_m_series_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_6143_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int ni_set_master_clock(struct comedi_device *dev, unsigned source, + unsigned period_ns); +static void ack_a_interrupt(struct comedi_device *dev, unsigned short a_status); +static void ack_b_interrupt(struct comedi_device *dev, unsigned short b_status); + +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; + break; + case 1: + return NI_GPCT1_SUBDEV; + break; + 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 handle_a_interrupt(struct comedi_device *dev, unsigned short status, + unsigned ai_mite_status); +static void handle_b_interrupt(struct comedi_device *dev, unsigned short status, + unsigned ao_mite_status); +static void get_last_sample_611x(struct comedi_device *dev); +static void get_last_sample_6143(struct comedi_device *dev); + +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; + devpriv->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; + devpriv->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; + devpriv->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(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(devpriv->g0_g1_select_reg, G0_G1_Select); + break; + default: + printk("Warning %s() called with invalid register\n", __func__); + printk("reg is %d\n", reg); + break; + } + mmiowb(); + spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags); +} + +#ifdef PCIDMA +static int ni_ai_drain_dma(struct comedi_device *dev); + +/* 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(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 == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for analog input."); + 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 == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for analog outut."); + 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 == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for counter."); + 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 == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for correlated digital outut."); + 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 */ +} + +/* e-series boards use the second irq signals to generate dma requests for their counters */ +#ifdef PCIDMA +static void ni_e_series_enable_second_irq(struct comedi_device *dev, + unsigned gpct_index, short enable) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + + if (board->reg_type & ni_reg_m_series_mask) + return; + switch (gpct_index) { + case 0: + if (enable) { + devpriv->stc_writew(dev, G0_Gate_Second_Irq_Enable, + Second_IRQ_A_Enable_Register); + } else { + devpriv->stc_writew(dev, 0, + Second_IRQ_A_Enable_Register); + } + break; + case 1: + if (enable) { + devpriv->stc_writew(dev, G1_Gate_Second_Irq_Enable, + Second_IRQ_B_Enable_Register); + } else { + devpriv->stc_writew(dev, 0, + Second_IRQ_B_Enable_Register); + } + break; + default: + BUG(); + break; + } +} +#endif /* PCIDMA */ + +static void ni_clear_ai_fifo(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + static const int timeout = 10000; + int i; + + if (board->reg_type == ni_reg_6143) { + /* Flush the 6143 data FIFO */ + ni_writel(0x10, AIFIFO_Control_6143); /* Flush fifo */ + ni_writel(0x00, AIFIFO_Control_6143); /* Flush fifo */ + /* Wait for complete */ + for (i = 0; i < timeout; i++) { + if (!(ni_readl(AIFIFO_Status_6143) & 0x10)) + break; + udelay(1); + } + if (i == timeout) { + comedi_error(dev, "FIFO flush timeout."); + } + } else { + devpriv->stc_writew(dev, 1, ADC_FIFO_Clear); + if (board->reg_type == ni_reg_625x) { + ni_writeb(0, M_Offset_Static_AI_Control(0)); + ni_writeb(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. */ + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); +#endif + } + } +} + +static void win_out2(struct comedi_device *dev, uint32_t data, int reg) +{ + struct ni_private *devpriv = dev->private; + + devpriv->stc_writew(dev, data >> 16, reg); + devpriv->stc_writew(dev, data & 0xffff, reg + 1); +} + +static uint32_t win_in2(struct comedi_device *dev, int reg) +{ + struct ni_private *devpriv = dev->private; + uint32_t bits; + + bits = devpriv->stc_readw(dev, reg) << 16; + bits |= devpriv->stc_readw(dev, reg + 1); + return bits; +} + +#define ao_win_out(data, addr) ni_ao_win_outw(dev, data, addr) +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(addr, AO_Window_Address_611x); + ni_writew(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(addr, AO_Window_Address_611x); + ni_writel(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(addr, AO_Window_Address_611x); + data = ni_readw(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); +} + +static irqreturn_t ni_E_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct ni_private *devpriv = dev->private; + 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 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 = devpriv->stc_readw(dev, AI_Status_1_Register); + b_status = devpriv->stc_readw(dev, AO_Status_1_Register); +#ifdef PCIDMA + if (mite) { + 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; +} + +#ifdef PCIDMA +static void ni_sync_ai_dma(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AI_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 void mite_handle_b_linkc(struct mite_struct *mite, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AO_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) +{ + struct ni_private *devpriv = dev->private; + static const int timeout = 10000; + int i; + for (i = 0; i < timeout; i++) { + unsigned short b_status; + + b_status = devpriv->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) { + comedi_error(dev, "timed out waiting for dma load"); + return -EPIPE; + } + return 0; +} + +#endif /* PCIDMA */ +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 shutdown_ai_command(struct comedi_device *dev) +{ + struct comedi_subdevice *s = &dev->subdevices[NI_AI_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 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); + cfc_handle_events(dev, s); +#endif +} + +static void ack_a_interrupt(struct comedi_device *dev, unsigned short a_status) +{ + struct ni_private *devpriv = dev->private; + 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) + devpriv->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 ni_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV]; + + /* 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)) { + printk + ("unknown mite interrupt, ack! (ai_mite_status=%08x)\n", + ai_mite_status); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + /* 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) { + printk + ("ni_mio_common: a_status=0xffff. 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_CB_EOA; + cfc_handle_events(dev, s); + } + return; + } + if (status & (AI_Overrun_St | AI_Overflow_St | + AI_SC_TC_Error_St)) { + printk("ni_mio_common: 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; + + cfc_handle_events(dev, s); + return; + } + if (status & AI_SC_TC_St) { + if (!devpriv->ai_continuous) + 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 ((devpriv->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); + + cfc_handle_events(dev, s); +} + +static void ack_b_interrupt(struct comedi_device *dev, unsigned short b_status) +{ + struct ni_private *devpriv = dev->private; + 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) + devpriv->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 ni_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AO_SUBDEV]; + /* unsigned short ack=0; */ + +#ifdef PCIDMA + /* Currently, mite.c requires us to handle LINKC */ + if (ao_mite_status & CHSR_LINKC) + 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)) { + printk + ("unknown mite interrupt, ack! (ao_mite_status=%08x)\n", + ao_mite_status); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } +#endif + + if (b_status == 0xffff) + return; + if (b_status & AO_Overrun_St) { + printk + ("ni_mio_common: AO FIFO underrun status=0x%04x status2=0x%04x\n", + b_status, devpriv->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) { + printk("ni_mio_common: 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 + + cfc_handle_events(dev, s); +} + +#ifndef PCIDMA + +static void ni_ao_fifo_load(struct comedi_device *dev, + struct comedi_subdevice *s, int n) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int chan; + int i; + unsigned short d; + u32 packed_data; + int range; + int err = 1; + + chan = async->cur_chan; + for (i = 0; i < n; i++) { + err &= comedi_buf_get(s, &d); + if (err == 0) + break; + + range = CR_RANGE(cmd->chanlist[chan]); + + if (board->reg_type & ni_reg_6xxx_mask) { + packed_data = d & 0xffff; + /* 6711 only has 16 bit wide ao fifo */ + if (board->reg_type != ni_reg_6711) { + err &= comedi_buf_get(s, &d); + if (err == 0) + break; + chan++; + i++; + packed_data |= (d << 16) & 0xffff0000; + } + ni_writel(packed_data, DAC_FIFO_Data_611x); + } else { + ni_writew(d, DAC_FIFO_Data); + } + chan++; + chan %= cmd->chanlist_len; + } + async->cur_chan = chan; + if (err == 0) + async->events |= COMEDI_CB_OVERFLOW; +} + +/* + * 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 = comedi_board(dev); + int n; + + n = comedi_buf_read_n_available(s); + if (n == 0) { + s->async->events |= COMEDI_CB_OVERFLOW; + return 0; + } + + n /= sizeof(short); + if (n > board->ao_fifo_depth / 2) + n = board->ao_fifo_depth / 2; + + ni_ao_fifo_load(dev, s, n); + + s->async->events |= COMEDI_CB_BLOCK; + + return 1; +} + +static int ni_ao_prep_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + int n; + + /* reset fifo */ + devpriv->stc_writew(dev, 1, DAC_FIFO_Clear); + if (board->reg_type & ni_reg_6xxx_mask) + ni_ao_win_outl(dev, 0x6, AO_FIFO_Offset_Load_611x); + + /* load some data */ + n = comedi_buf_read_n_available(s); + if (n == 0) + return 0; + + n /= sizeof(short); + if (n > board->ao_fifo_depth) + n = board->ao_fifo_depth; + + ni_ao_fifo_load(dev, s, n); + + return n; +} + +static void ni_ai_fifo_read(struct comedi_device *dev, + struct comedi_subdevice *s, int n) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + struct comedi_async *async = s->async; + int i; + + if (board->reg_type == ni_reg_611x) { + unsigned short data[2]; + u32 dl; + + for (i = 0; i < n / 2; i++) { + dl = ni_readl(ADC_FIFO_Data_611x); + /* This may get the hi/lo data in the wrong order */ + data[0] = (dl >> 16) & 0xffff; + data[1] = dl & 0xffff; + cfc_write_array_to_buffer(s, data, sizeof(data)); + } + /* Check if there's a single sample stuck in the FIFO */ + if (n % 2) { + dl = ni_readl(ADC_FIFO_Data_611x); + data[0] = dl & 0xffff; + cfc_write_to_buffer(s, data[0]); + } + } else if (board->reg_type == ni_reg_6143) { + unsigned short data[2]; + u32 dl; + + /* 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(AIFIFO_Data_6143); + + data[0] = (dl >> 16) & 0xffff; + data[1] = dl & 0xffff; + cfc_write_array_to_buffer(s, data, sizeof(data)); + } + if (n % 2) { + /* Assume there is a single sample stuck in the FIFO */ + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + data[0] = (dl >> 16) & 0xffff; + cfc_write_to_buffer(s, data[0]); + } + } else { + if (n > sizeof(devpriv->ai_fifo_buffer) / + sizeof(devpriv->ai_fifo_buffer[0])) { + comedi_error(dev, "bug! ai_fifo_buffer too small"); + async->events |= COMEDI_CB_ERROR; + return; + } + for (i = 0; i < n; i++) { + devpriv->ai_fifo_buffer[i] = + ni_readw(ADC_FIFO_Data_Register); + } + cfc_write_array_to_buffer(s, devpriv->ai_fifo_buffer, + n * + sizeof(devpriv->ai_fifo_buffer[0])); + } +} + +static void ni_handle_fifo_half_full(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV]; + int n; + + n = board->ai_fifo_depth / 2; + + ni_ai_fifo_read(dev, s, n); +} +#endif + +#ifdef PCIDMA +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 ((devpriv->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) { + printk("ni_mio_common: wait for dma drain timed out\n"); + printk + ("mite_bytes_in_transit=%i, AI_Status1_Register=0x%x\n", + mite_bytes_in_transit(devpriv->ai_mite_chan), + devpriv->stc_readw(dev, AI_Status_1_Register)); + retval = -1; + } + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + ni_sync_ai_dma(dev); + + return retval; +} +#endif +/* + Empties the AI fifo +*/ +static void ni_handle_fifo_dregs(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV]; + unsigned short data[2]; + u32 dl; + unsigned short fifo_empty; + int i; + + if (board->reg_type == ni_reg_611x) { + while ((devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St) == 0) { + dl = ni_readl(ADC_FIFO_Data_611x); + + /* This may get the hi/lo data in the wrong order */ + data[0] = (dl >> 16); + data[1] = (dl & 0xffff); + cfc_write_array_to_buffer(s, data, sizeof(data)); + } + } else if (board->reg_type == ni_reg_6143) { + i = 0; + while (ni_readl(AIFIFO_Status_6143) & 0x04) { + dl = ni_readl(AIFIFO_Data_6143); + + /* This may get the hi/lo data in the wrong order */ + data[0] = (dl >> 16); + data[1] = (dl & 0xffff); + cfc_write_array_to_buffer(s, data, sizeof(data)); + i += 2; + } + /* Check if stranded sample is present */ + if (ni_readl(AIFIFO_Status_6143) & 0x01) { + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + data[0] = (dl >> 16) & 0xffff; + cfc_write_to_buffer(s, data[0]); + } + + } else { + fifo_empty = + devpriv->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 = + devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St; + if (fifo_empty) + break; + devpriv->ai_fifo_buffer[i] = + ni_readw(ADC_FIFO_Data_Register); + } + cfc_write_array_to_buffer(s, devpriv->ai_fifo_buffer, + i * + sizeof(devpriv-> + ai_fifo_buffer[0])); + } + } +} + +static void get_last_sample_611x(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv __maybe_unused = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV]; + unsigned short data; + u32 dl; + + if (board->reg_type != ni_reg_611x) + return; + + /* Check if there's a single sample stuck in the FIFO */ + if (ni_readb(XXX_Status) & 0x80) { + dl = ni_readl(ADC_FIFO_Data_611x); + data = (dl & 0xffff); + cfc_write_to_buffer(s, data); + } +} + +static void get_last_sample_6143(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv __maybe_unused = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV]; + unsigned short data; + u32 dl; + + if (board->reg_type != ni_reg_6143) + return; + + /* Check if there's a single sample stuck in the FIFO */ + if (ni_readl(AIFIFO_Status_6143) & 0x01) { + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + + /* This may get the hi/lo data in the wrong order */ + data = (dl >> 16) & 0xffff; + cfc_write_to_buffer(s, data); + } +} + +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 length = num_bytes / bytes_per_sample(s); + unsigned short *array = data; + unsigned int *larray = data; + unsigned int i; + + for (i = 0; i < length; 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) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV]; + int retval; + unsigned long flags; + + retval = ni_request_ai_mite_channel(dev); + if (retval) + return retval; +/* printk("comedi_debug: using mite channel %i for ai.\n", devpriv->ai_mite_chan->channel); */ + + /* 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 == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return -EIO; + } + + switch (board->reg_type) { + case ni_reg_611x: + case ni_reg_6143: + mite_prep_dma(devpriv->ai_mite_chan, 32, 16); + break; + case ni_reg_628x: + mite_prep_dma(devpriv->ai_mite_chan, 32, 32); + break; + default: + mite_prep_dma(devpriv->ai_mite_chan, 16, 16); + break; + } + /*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) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[NI_AO_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 (board->reg_type & (ni_reg_611x | ni_reg_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) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + + ni_release_ai_mite_channel(dev); + /* ai configuration */ + devpriv->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 (board->reg_type != ni_reg_6143) + ni_writeb(0, Misc_Command); + + devpriv->stc_writew(dev, AI_Disarm, AI_Command_1_Register); /* reset pulses */ + devpriv->stc_writew(dev, + AI_Start_Stop | AI_Mode_1_Reserved + /*| AI_Trigger_Once */ , + AI_Mode_1_Register); + devpriv->stc_writew(dev, 0x0000, AI_Mode_2_Register); + /* generate FIFO interrupts on non-empty */ + devpriv->stc_writew(dev, (0 << 6) | 0x0000, AI_Mode_3_Register); + if (board->reg_type == ni_reg_611x) { + devpriv->stc_writew(dev, AI_SHIFTIN_Pulse_Width | + AI_SOC_Polarity | + AI_LOCALMUX_CLK_Pulse_Width, + AI_Personal_Register); + devpriv->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 (board->reg_type == ni_reg_6143) { + devpriv->stc_writew(dev, AI_SHIFTIN_Pulse_Width | + AI_SOC_Polarity | + AI_LOCALMUX_CLK_Pulse_Width, + AI_Personal_Register); + devpriv->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; + devpriv->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 (board->reg_type == ni_reg_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); + devpriv->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 + */ + devpriv->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 */ + + devpriv->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 = s->async->buf_write_count - s->async->buf_read_count; + spin_unlock_irqrestore(&dev->spinlock, flags); + + return count; +} + +static int ni_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + int i, n; + const unsigned int mask = (1 << board->adbits) - 1; + unsigned signbits; + unsigned short d; + unsigned long dl; + + ni_load_channelgain_list(dev, 1, &insn->chanspec); + + ni_clear_ai_fifo(dev); + + signbits = devpriv->ai_offset[0]; + if (board->reg_type == ni_reg_611x) { + for (n = 0; n < num_adc_stages_611x; n++) { + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + udelay(1); + } + for (n = 0; n < insn->n; n++) { + devpriv->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(XXX_Status) & 0x80) { + d = (ni_readl(ADC_FIFO_Data_611x) >> 16) + & 0xffff; + break; + } + if (!(devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St)) { + d = ni_readl(ADC_FIFO_Data_611x) & + 0xffff; + break; + } + } + if (i == NI_TIMEOUT) { + printk + ("ni_mio_common: timeout in 611x ni_ai_insn_read\n"); + return -ETIME; + } + d += signbits; + data[n] = d; + } + } else if (board->reg_type == ni_reg_6143) { + for (n = 0; n < insn->n; n++) { + devpriv->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(AIFIFO_Status_6143) & 0x01) { + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + break; + } + } + if (i == NI_TIMEOUT) { + printk + ("ni_mio_common: timeout in 6143 ni_ai_insn_read\n"); + return -ETIME; + } + data[n] = (((dl >> 16) & 0xFFFF) + signbits) & 0xFFFF; + } + } else { + for (n = 0; n < insn->n; n++) { + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + for (i = 0; i < NI_TIMEOUT; i++) { + if (!(devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St)) + break; + } + if (i == NI_TIMEOUT) { + printk + ("ni_mio_common: timeout in ni_ai_insn_read\n"); + return -ETIME; + } + if (board->reg_type & ni_reg_m_series_mask) { + data[n] = + ni_readl(M_Offset_AI_FIFO_Data) & mask; + } else { + d = ni_readw(ADC_FIFO_Data_Register); + d += signbits; /* subtle: needs to be short addition */ + data[n] = d; + } + } + } + return insn->n; +} + +static void ni_prime_channelgain_list(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int i; + + devpriv->stc_writew(dev, AI_CONVERT_Pulse, AI_Command_1_Register); + for (i = 0; i < NI_TIMEOUT; ++i) { + if (!(devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St)) { + devpriv->stc_writew(dev, 1, ADC_FIFO_Clear); + return; + } + udelay(1); + } + printk("ni_mio_common: 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 = comedi_board(dev); + struct ni_private *devpriv = dev->private; + unsigned int chan, range, aref; + unsigned int i; + unsigned offset; + unsigned int dither; + unsigned range_code; + + devpriv->stc_writew(dev, 1, Configuration_Memory_Clear); + +/* offset = 1 << (board->adbits - 1); */ + 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(bypass_bits, M_Offset_AI_Config_FIFO_Bypass); + } else { + ni_writel(0, M_Offset_AI_Config_FIFO_Bypass); + } + offset = 0; + 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] = offset; + 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(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, + unsigned int n_chan, unsigned int *list) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + unsigned int chan, range, aref; + unsigned int i; + unsigned int hi, lo; + unsigned offset; + unsigned int dither; + + if (board->reg_type & ni_reg_m_series_mask) { + ni_m_series_load_channelgain_list(dev, n_chan, list); + return; + } + if (n_chan == 1 && (board->reg_type != ni_reg_611x) + && (board->reg_type != ni_reg_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; + } + + devpriv->stc_writew(dev, 1, Configuration_Memory_Clear); + + /* Set up Calibration mode if required */ + if (board->reg_type == ni_reg_6143) { + if ((list[0] & CR_ALT_SOURCE) + && !devpriv->ai_calib_source_enabled) { + /* Strobe Relay enable bit */ + ni_writew(devpriv->ai_calib_source | + Calibration_Channel_6143_RelayOn, + Calibration_Channel_6143); + ni_writew(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(devpriv->ai_calib_source | + Calibration_Channel_6143_RelayOff, + Calibration_Channel_6143); + ni_writew(devpriv->ai_calib_source, + Calibration_Channel_6143); + devpriv->ai_calib_source_enabled = 0; + msleep_interruptible(100); /* Allow relays to change */ + } + } + + offset = 1 << (board->adbits - 1); + for (i = 0; i < n_chan; i++) { + if ((board->reg_type != ni_reg_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 (board->reg_type == ni_reg_611x) + devpriv->ai_offset[i] = offset; + else + devpriv->ai_offset[i] = (range & 0x100) ? 0 : offset; + + hi = 0; + if ((list[i] & CR_ALT_SOURCE)) { + if (board->reg_type == ni_reg_611x) + ni_writew(CR_CHAN(list[i]) & 0x0003, + Calibration_Channel_Select_611x); + } else { + if (board->reg_type == ni_reg_611x) + aref = AREF_DIFF; + else if (board->reg_type == ni_reg_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(hi, Configuration_Memory_High); + + if (board->reg_type != ni_reg_6143) { + lo = range; + if (i == n_chan - 1) + lo |= AI_LAST_CHANNEL; + if (dither) + lo |= AI_DITHER; + + ni_writew(lo, Configuration_Memory_Low); + } + } + + /* prime the channel/gain list */ + if ((board->reg_type != ni_reg_611x) + && (board->reg_type != ni_reg_6143)) { + ni_prime_channelgain_list(dev); + } +} + +static int ni_ns_to_timer(const struct comedi_device *dev, unsigned nanosec, + int round_mode) +{ + struct ni_private *devpriv = dev->private; + int divider; + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (nanosec + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + divider = (nanosec) / devpriv->clock_ns; + break; + case TRIG_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 = comedi_board(dev); + + switch (board->reg_type) { + case ni_reg_611x: + case ni_reg_6143: + /* simultaneously-sampled inputs */ + return board->ai_speed; + break; + default: + /* multiplexed inputs */ + break; + } + 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 = comedi_board(dev); + struct ni_private *devpriv = dev->private; + int err = 0; + unsigned int tmp; + unsigned int sources; + + /* Step 1 : check if triggers are trivially valid */ + + if ((cmd->flags & CMDF_WRITE)) + cmd->flags &= ~CMDF_WRITE; + + err |= cfc_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_INT | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + + sources = TRIG_TIMER | TRIG_EXT; + if (board->reg_type == ni_reg_611x || + board->reg_type == ni_reg_6143) + sources |= TRIG_NOW; + err |= cfc_check_trigger_src(&cmd->convert_src, sources); + + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, tmp); + break; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + ni_min_ai_scan_period_ns(dev, cmd->chanlist_len)); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, tmp); + } else { /* TRIG_OTHER */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } + + if (cmd->convert_src == TRIG_TIMER) { + if ((board->reg_type == ni_reg_611x) + || (board->reg_type == ni_reg_6143)) { + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + } else { + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->convert_arg, tmp); + } else if (cmd->convert_src == TRIG_NOW) { + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + unsigned int max_count = 0x01000000; + + if (board->reg_type == ni_reg_611x) + max_count -= num_adc_stages_611x; + err |= cfc_check_trigger_arg_max(&cmd->stop_arg, max_count); + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + } else { + /* TRIG_NONE */ + err |= cfc_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 & + TRIG_ROUND_MASK)); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + if ((board->reg_type != ni_reg_611x) + && (board->reg_type != ni_reg_6143)) { + tmp = cmd->convert_arg; + cmd->convert_arg = + ni_timer_to_ns(dev, ni_ns_to_timer(dev, + cmd->convert_arg, + cmd-> + flags & + TRIG_ROUND_MASK)); + 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_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct ni_board_struct *board = comedi_board(dev); + 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) { + comedi_error(dev, "cannot run command without an irq"); + return -EIO; + } + ni_clear_ai_fifo(dev); + + ni_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist); + + /* start configuration */ + devpriv->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; + devpriv->stc_writew(dev, devpriv->an_trig_etc_reg, + Analog_Trigger_Etc_Register); + + switch (cmd->start_src) { + case TRIG_INT: + case TRIG_NOW: + devpriv->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; + devpriv->stc_writew(dev, bits, + AI_Trigger_Select_Register); + break; + } + } + + mode2 &= ~AI_Pre_Trigger; + mode2 &= ~AI_SC_Initial_Load_Source; + mode2 &= ~AI_SC_Reload_Mode; + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + + if (cmd->chanlist_len == 1 || (board->reg_type == ni_reg_611x) + || (board->reg_type == ni_reg_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 */ + } + devpriv->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 (board->reg_type == ni_reg_611x) { + /* have to take 3 stage adc pipeline into account */ + stop_count += num_adc_stages_611x; + } + /* stage number of scans */ + devpriv->stc_writel(dev, stop_count, AI_SC_Load_A_Registers); + + mode1 |= AI_Start_Stop | AI_Mode_1_Reserved | AI_Trigger_Once; + devpriv->stc_writew(dev, mode1, AI_Mode_1_Register); + /* load SC (Scan Count) */ + devpriv->stc_writew(dev, AI_SC_Load, AI_Command_1_Register); + + devpriv->ai_continuous = 0; + 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 */ + devpriv->stc_writel(dev, 0, AI_SC_Load_A_Registers); + + mode1 |= AI_Start_Stop | AI_Mode_1_Reserved | AI_Continuous; + devpriv->stc_writew(dev, mode1, AI_Mode_1_Register); + + /* load SC (Scan Count) */ + devpriv->stc_writew(dev, AI_SC_Load, AI_Command_1_Register); + + devpriv->ai_continuous = 1; + + 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; + devpriv->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; */ + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + + /* load SI */ + timer = ni_ns_to_timer(dev, cmd->scan_begin_arg, + TRIG_ROUND_NEAREST); + devpriv->stc_writel(dev, timer, AI_SI_Load_A_Registers); + devpriv->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)); + devpriv->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, + TRIG_ROUND_NEAREST); + devpriv->stc_writew(dev, 1, AI_SI2_Load_A_Register); /* 0,0 does not work. */ + devpriv->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; + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + + /* AI_SI2_Load */ + devpriv->stc_writew(dev, AI_SI2_Load, AI_Command_1_Register); + + mode2 |= AI_SI2_Reload_Mode; /* alternate */ + mode2 |= AI_SI2_Initial_Load_Source; /* B */ + + devpriv->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; + devpriv->stc_writew(dev, mode1, AI_Mode_1_Register); + + mode2 |= AI_Start_Stop_Gate_Enable | AI_SC_Gate_Enable; + devpriv->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 & TRIG_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 + devpriv->stc_writew(dev, AI_FIFO_Mode_HF_to_E, + AI_Mode_3_Register); +#else + devpriv->stc_writew(dev, AI_FIFO_Mode_HF, + AI_Mode_3_Register); +#endif + break; + case AIMODE_SAMPLE: + /*generate FIFO interrupts on non-empty */ + devpriv->stc_writew(dev, AI_FIFO_Mode_NE, + AI_Mode_3_Register); + break; + case AIMODE_SCAN: +#ifdef PCIDMA + devpriv->stc_writew(dev, AI_FIFO_Mode_NE, + AI_Mode_3_Register); +#else + devpriv->stc_writew(dev, AI_FIFO_Mode_HF, + AI_Mode_3_Register); +#endif + interrupt_a_enable |= AI_STOP_Interrupt_Enable; + break; + default: + break; + } + + devpriv->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); /* clear interrupts */ + + 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 */ + devpriv->stc_writew(dev, AI_Configuration_End, Joint_Reset_Register); + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + devpriv->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? */ + devpriv->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 */ + devpriv->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_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; + + devpriv->stc_writew(dev, AI_START1_Pulse | devpriv->ai_cmd2, + AI_Command_2_Register); + s->async->inttrig = NULL; + + return 1; +} + +static int ni_ai_config_analog_trig(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int ni_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + + if (insn->n < 1) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_ANALOG_TRIG: + return ni_ai_config_analog_trig(dev, s, insn, data); + case INSN_CONFIG_ALT_SOURCE: + if (board->reg_type & ni_reg_m_series_mask) { + 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 (board->reg_type == ni_reg_6143) { + unsigned int calib_source; + + calib_source = data[1] & 0xf; + + if (calib_source > 0xF) + return -EINVAL; + + devpriv->ai_calib_source = calib_source; + ni_writew(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 (board->reg_type == ni_reg_611x) { + ni_writeb(calib_source_adjust, + Cal_Gain_Select_611x); + } + } + return 2; + default: + break; + } + + return -EINVAL; +} + +static int ni_ai_config_analog_trig(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + unsigned int a, b, modebits; + int err = 0; + + /* data[1] is flags + * data[2] is analog line + * data[3] is set level + * data[4] is reset level */ + if (!board->has_analog_trig) + return -EINVAL; + if ((data[1] & 0xffff0000) != COMEDI_EV_SCAN_BEGIN) { + data[1] &= (COMEDI_EV_SCAN_BEGIN | 0xffff); + err++; + } + if (data[2] >= board->n_adchan) { + data[2] = board->n_adchan - 1; + err++; + } + if (data[3] > 255) { /* a */ + data[3] = 255; + err++; + } + if (data[4] > 255) { /* b */ + data[4] = 255; + err++; + } + /* + * 00 ignore + * 01 set + * 10 reset + * + * modes: + * 1 level: +b- +a- + * high mode 00 00 01 10 + * low mode 00 00 10 01 + * 2 level: (a<b) + * hysteresis low mode 10 00 00 01 + * hysteresis high mode 01 00 00 10 + * middle mode 10 01 01 10 + */ + + a = data[3]; + b = data[4]; + modebits = data[1] & 0xff; + if (modebits & 0xf0) { + /* two level mode */ + if (b < a) { + /* swap order */ + a = data[4]; + b = data[3]; + modebits = + ((data[1] & 0xf) << 4) | ((data[1] & 0xf0) >> 4); + } + devpriv->atrig_low = a; + devpriv->atrig_high = b; + switch (modebits) { + case 0x81: /* low hysteresis mode */ + devpriv->atrig_mode = 6; + break; + case 0x42: /* high hysteresis mode */ + devpriv->atrig_mode = 3; + break; + case 0x96: /* middle window mode */ + devpriv->atrig_mode = 2; + break; + default: + data[1] &= ~0xff; + err++; + } + } else { + /* one level mode */ + if (b != 0) { + data[4] = 0; + err++; + } + switch (modebits) { + case 0x06: /* high window mode */ + devpriv->atrig_high = a; + devpriv->atrig_mode = 0; + break; + case 0x09: /* low window mode */ + devpriv->atrig_low = a; + devpriv->atrig_mode = 1; + break; + default: + data[1] &= ~0xff; + err++; + } + } + if (err) + return -EAGAIN; + return 5; +} + +/* munge data from unsigned to 2's complement for analog output bipolar modes */ +static void ni_ao_munge(struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, + unsigned int chan_index) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int length = num_bytes / sizeof(short); + unsigned int offset = 1 << (board->aobits - 1); + unsigned short *array = data; + unsigned int range; + unsigned int i; + + for (i = 0; i < length; i++) { + range = CR_RANGE(cmd->chanlist[chan_index]); + if (board->ao_unipolar == 0 || (range & 1) == 0) + array[i] -= offset; +#ifdef PCIDMA + array[i] = cpu_to_le16(array[i]); +#endif + 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) +{ + const struct ni_board_struct *board = comedi_board(dev); + 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 < board->n_aochan; ++i) { + devpriv->ao_conf[i] &= ~MSeries_AO_Update_Timed_Bit; + ni_writeb(devpriv->ao_conf[i], + M_Offset_AO_Config_Bank(i)); + ni_writeb(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(0, M_Offset_AO_Reference_Attenuation(chan)); + break; + case 10000000: + conf |= MSeries_AO_DAC_Reference_5V_Internal_Bits; + ni_writeb(0, M_Offset_AO_Reference_Attenuation(chan)); + break; + case 4000000: + conf |= MSeries_AO_DAC_Reference_10V_Internal_Bits; + ni_writeb(MSeries_Attenuate_x5_Bit, + M_Offset_AO_Reference_Attenuation(chan)); + break; + case 2000000: + conf |= MSeries_AO_DAC_Reference_5V_Internal_Bits; + ni_writeb(MSeries_Attenuate_x5_Bit, + M_Offset_AO_Reference_Attenuation(chan)); + break; + default: + printk("%s: bug! unhandled ao reference voltage\n", + __func__); + 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: + printk("%s: bug! unhandled ao offset voltage\n", + __func__); + break; + } + if (timed) + conf |= MSeries_AO_Update_Timed_Bit; + ni_writeb(conf, M_Offset_AO_Config_Bank(chan)); + devpriv->ao_conf[chan] = conf; + ni_writeb(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) +{ + const struct ni_board_struct *board = comedi_board(dev); + 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 (board->ao_unipolar) { + if ((range & 1) == 0) { + conf |= AO_Bipolar; + invert = (1 << (board->aobits - 1)); + } else { + invert = 0; + } + if (range & 2) + conf |= AO_Ext_Ref; + } else { + conf |= AO_Bipolar; + invert = (1 << (board->aobits - 1)); + } + + /* 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(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) +{ + const struct ni_board_struct *board = comedi_board(dev); + + if (board->reg_type & ni_reg_m_series_mask) + 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_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->ao[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int ni_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int invert; + + invert = ni_ao_config_chanlist(dev, s, &insn->chanspec, 1, 0); + + devpriv->ao[chan] = data[0]; + + if (board->reg_type & ni_reg_m_series_mask) { + ni_writew(data[0], M_Offset_DAC_Direct_Data(chan)); + } else + ni_writew(data[0] ^ invert, + (chan) ? DAC1_Direct_Data : DAC0_Direct_Data); + + return 1; +} + +static int ni_ao_insn_write_671x(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int invert; + + ao_win_out(1 << chan, AO_Immediate_671x); + invert = 1 << (board->aobits - 1); + + ni_ao_config_chanlist(dev, s, &insn->chanspec, 1, 0); + + devpriv->ao[chan] = data[0]; + ao_win_out(data[0] ^ invert, DACx_Direct_Data_671x(chan)); + + return 1; +} + +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 = comedi_board(dev); + struct ni_private *devpriv = dev->private; + + switch (data[0]) { + case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE: + switch (data[1]) { + case COMEDI_OUTPUT: + data[2] = 1 + board->ao_fifo_depth * sizeof(short); + if (devpriv->mite) + data[2] += devpriv->mite->fifo_size; + break; + case COMEDI_INPUT: + data[2] = 0; + break; + default: + return -EINVAL; + break; + } + return 0; + default: + break; + } + + return -EINVAL; +} + +static int ni_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + const struct ni_board_struct *board __maybe_unused = comedi_board(dev); + 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 + devpriv->stc_writew(dev, 1, DAC_FIFO_Clear); + if (board->reg_type & ni_reg_6xxx_mask) + 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 + + devpriv->stc_writew(dev, devpriv->ao_mode3 | AO_Not_An_UPDATE, + AO_Mode_3_Register); + devpriv->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 ((devpriv->stc_readw(dev, + Joint_Status_2_Register) & + AO_TMRDACWRs_In_Progress_St) == 0) + break; + } + if (i == timeout) { + comedi_error(dev, + "timed out waiting for AO_TMRDACWRs_In_Progress_St to clear"); + return -EIO; + } + /* stc manual says we are need to clear error interrupt after AO_TMRDACWRs_In_Progress_St clears */ + devpriv->stc_writew(dev, AO_Error_Interrupt_Ack, + Interrupt_B_Ack_Register); + + ni_set_bits(dev, Interrupt_B_Enable_Register, interrupt_b_bits, 1); + + devpriv->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); + + devpriv->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 = comedi_board(dev); + struct ni_private *devpriv = dev->private; + const struct comedi_cmd *cmd = &s->async->cmd; + int bits; + int i; + unsigned trigvar; + + if (dev->irq == 0) { + comedi_error(dev, "cannot run command without an irq"); + return -EIO; + } + + devpriv->stc_writew(dev, AO_Configuration_Start, Joint_Reset_Register); + + devpriv->stc_writew(dev, AO_Disarm, AO_Command_1_Register); + + if (board->reg_type & ni_reg_6xxx_mask) { + ao_win_out(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; + ao_win_out(chan, AO_Waveform_Generation_611x); + } + ao_win_out(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; + } + devpriv->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; + devpriv->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 */ + devpriv->stc_writew(dev, devpriv->ao_trigger_select, + AO_Trigger_Select_Register); + break; + default: + BUG(); + break; + } + devpriv->ao_mode3 &= ~AO_Trigger_Length; + devpriv->stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register); + + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + devpriv->ao_mode2 &= ~AO_BC_Initial_Load_Source; + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + if (cmd->stop_src == TRIG_NONE) + devpriv->stc_writel(dev, 0xffffff, AO_BC_Load_A_Register); + else + devpriv->stc_writel(dev, 0, AO_BC_Load_A_Register); + devpriv->stc_writew(dev, AO_BC_Load, AO_Command_1_Register); + devpriv->ao_mode2 &= ~AO_UC_Initial_Load_Source; + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + switch (cmd->stop_src) { + case TRIG_COUNT: + if (board->reg_type & ni_reg_m_series_mask) { + /* this is how the NI example code does it for m-series boards, verified correct with 6259 */ + devpriv->stc_writel(dev, cmd->stop_arg - 1, + AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, + AO_Command_1_Register); + } else { + devpriv->stc_writel(dev, cmd->stop_arg, + AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, + AO_Command_1_Register); + devpriv->stc_writel(dev, cmd->stop_arg - 1, + AO_UC_Load_A_Register); + } + break; + case TRIG_NONE: + devpriv->stc_writel(dev, 0xffffff, AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, AO_Command_1_Register); + devpriv->stc_writel(dev, 0xffffff, AO_UC_Load_A_Register); + break; + default: + devpriv->stc_writel(dev, 0, AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, AO_Command_1_Register); + devpriv->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, + TRIG_ROUND_NEAREST); + devpriv->stc_writel(dev, 1, AO_UI_Load_A_Register); + devpriv->stc_writew(dev, AO_UI_Load, AO_Command_1_Register); + devpriv->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; + } + devpriv->stc_writew(dev, devpriv->ao_cmd2, AO_Command_2_Register); + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + devpriv->ao_mode2 &= + ~(AO_UI_Reload_Mode(3) | AO_UI_Initial_Load_Source); + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + + if (cmd->scan_end_arg > 1) { + devpriv->ao_mode1 |= AO_Multiple_Channels; + devpriv->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 (board->reg_type & + (ni_reg_m_series_mask | ni_reg_6xxx_mask)) { + bits |= AO_Number_Of_Channels(0); + } else { + bits |= + AO_Number_Of_Channels(CR_CHAN(cmd->chanlist[0])); + } + devpriv->stc_writew(dev, bits, AO_Output_Control_Register); + } + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + + devpriv->stc_writew(dev, AO_DAC0_Update_Mode | AO_DAC1_Update_Mode, + AO_Command_1_Register); + + devpriv->ao_mode3 |= AO_Stop_On_Overrun_Error; + devpriv->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; + devpriv->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 (board->reg_type & ni_reg_m_series_mask) + bits |= AO_Number_Of_DAC_Packages; +#endif + devpriv->stc_writew(dev, bits, AO_Personal_Register); + /* enable sending of ao dma requests */ + devpriv->stc_writew(dev, AO_AOFREQ_Enable, AO_Start_Select_Register); + + devpriv->stc_writew(dev, AO_Configuration_End, Joint_Reset_Register); + + if (cmd->stop_src == TRIG_COUNT) { + devpriv->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 = comedi_board(dev); + struct ni_private *devpriv = dev->private; + int err = 0; + unsigned int tmp; + + /* Step 1 : check if triggers are trivially valid */ + + if ((cmd->flags & CMDF_WRITE) == 0) + cmd->flags |= CMDF_WRITE; + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, tmp); + break; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ao_speed); + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, + devpriv->clock_ns * 0xffffff); + } + + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + else /* TRIG_NONE */ + err |= cfc_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 & + TRIG_ROUND_MASK)); + 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) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + + /* devpriv->ao0p=0x0000; */ + /* ni_writew(devpriv->ao0p,AO_Configuration); */ + + /* devpriv->ao1p=AO_Channel(1); */ + /* ni_writew(devpriv->ao1p,AO_Configuration); */ + + ni_release_ao_mite_channel(dev); + + devpriv->stc_writew(dev, AO_Configuration_Start, Joint_Reset_Register); + devpriv->stc_writew(dev, AO_Disarm, AO_Command_1_Register); + ni_set_bits(dev, Interrupt_B_Enable_Register, ~0, 0); + devpriv->stc_writew(dev, AO_BC_Source_Select, AO_Personal_Register); + devpriv->stc_writew(dev, 0x3f98, Interrupt_B_Ack_Register); + devpriv->stc_writew(dev, AO_BC_Source_Select | AO_UPDATE_Pulse_Width | + AO_TMRDACWR_Pulse_Width, AO_Personal_Register); + devpriv->stc_writew(dev, 0, AO_Output_Control_Register); + devpriv->stc_writew(dev, 0, AO_Start_Select_Register); + devpriv->ao_cmd1 = 0; + devpriv->stc_writew(dev, devpriv->ao_cmd1, AO_Command_1_Register); + devpriv->ao_cmd2 = 0; + devpriv->stc_writew(dev, devpriv->ao_cmd2, AO_Command_2_Register); + devpriv->ao_mode1 = 0; + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + devpriv->ao_mode2 = 0; + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + if (board->reg_type & ni_reg_m_series_mask) + devpriv->ao_mode3 = AO_Last_Gate_Disable; + else + devpriv->ao_mode3 = 0; + devpriv->stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register); + devpriv->ao_trigger_select = 0; + devpriv->stc_writew(dev, devpriv->ao_trigger_select, + AO_Trigger_Select_Register); + if (board->reg_type & ni_reg_6xxx_mask) { + unsigned immediate_bits = 0; + unsigned i; + for (i = 0; i < s->n_chan; ++i) + immediate_bits |= 1 << i; + ao_win_out(immediate_bits, AO_Immediate_671x); + ao_win_out(CLEAR_WG, AO_Misc_611x); + } + devpriv->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); + devpriv->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); + devpriv->stc_writew(dev, devpriv->dio_output, + DIO_Output_Register); + } + + data[1] = devpriv->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) +{ + struct ni_private *devpriv __maybe_unused = dev->private; + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + ni_writel(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) +{ + struct ni_private *devpriv __maybe_unused = dev->private; + + if (comedi_dio_update_state(s, data)) + ni_writel(s->state, M_Offset_Static_Digital_Output); + + data[1] = ni_readl(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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + /* 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_cdio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_private *devpriv __maybe_unused = dev->private; + 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(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(cdo_mode_bits, M_Offset_CDO_Mode); + if (s->io_bits) { + ni_writel(s->state, M_Offset_CDO_FIFO_Data); + ni_writel(CDO_SW_Update_Bit, M_Offset_CDIO_Command); + ni_writel(s->io_bits, M_Offset_CDO_Mask_Enable); + } else { + comedi_error(dev, + "attempted to run digital output command with no lines configured as outputs"); + 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_cdo_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ +#ifdef PCIDMA + struct ni_private *devpriv = dev->private; + unsigned long flags; +#endif + struct comedi_cmd *cmd = &s->async->cmd; + int retval = 0; + unsigned i; + const unsigned timeout = 1000; + + 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 { + comedi_error(dev, "BUG: no cdo mite channel?"); + 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(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(M_Offset_CDIO_Status) & CDO_FIFO_Full_Bit) + break; + udelay(10); + } + if (i == timeout) { + comedi_error(dev, "dma failed to fill cdo fifo!"); + ni_cdio_cancel(dev, s); + return -EIO; + } + ni_writel(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_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_private *devpriv __maybe_unused = dev->private; + + ni_writel(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(0, +* M_Offset_Interrupt_C_Enable); +*/ + ni_writel(0, M_Offset_CDO_Mask_Enable); + ni_release_cdo_mite_channel(dev); + return 0; +} + +static void handle_cdio_interrupt(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv __maybe_unused = dev->private; + unsigned cdio_status; + struct comedi_subdevice *s = &dev->subdevices[NI_DIO_SUBDEV]; +#ifdef PCIDMA + unsigned long flags; +#endif + + if ((board->reg_type & ni_reg_m_series_mask) == 0) + 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(M_Offset_CDIO_Status); + if (cdio_status & (CDO_Overrun_Bit | CDO_Underflow_Bit)) { + /* printk("cdio error: statux=0x%x\n", cdio_status); */ + ni_writel(CDO_Error_Interrupt_Confirm_Bit, M_Offset_CDIO_Command); /* XXX just guessing this is needed and does something useful */ + s->async->events |= COMEDI_CB_OVERFLOW; + } + if (cdio_status & CDO_FIFO_Empty_Bit) { + /* printk("cdio fifo empty\n"); */ + ni_writel(CDO_Empty_FIFO_Interrupt_Enable_Clear_Bit, + M_Offset_CDIO_Command); + /* s->async->events |= COMEDI_CB_EOA; */ + } + cfc_handle_events(dev, s); +} + +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]; + } + + devpriv->stc_writew(dev, devpriv->dio_control, + DIO_Control_Register); + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + return 1; + + break; + + 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 { + printk("ni_serial_insn_config: serial disabled!\n"); + return -EINVAL; + } + if (err < 0) + return err; + data[1] = byte_in & 0xFF; + return insn->n; + + break; + default: + return -EINVAL; + } + +} + +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); + devpriv->stc_writew(dev, devpriv->dio_output, DIO_Output_Register); + + status1 = devpriv->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; + devpriv->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 = + devpriv->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) { + printk + ("ni_serial_hw_readwrite8: 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 != NULL) + *data_in = devpriv->stc_readw(dev, DIO_Serial_Input_Register); + +Error: + devpriv->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; + devpriv->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; + devpriv->stc_writew(dev, devpriv->dio_control, + DIO_Control_Register); + + udelay((devpriv->serial_interval_ns + 999) / 2000); + + devpriv->dio_control &= ~DIO_Software_Serial_Control; + devpriv->stc_writew(dev, devpriv->dio_control, + DIO_Control_Register); + + udelay((devpriv->serial_interval_ns + 999) / 2000); + + /* Input current bit */ + if (devpriv->stc_readw(dev, + DIO_Parallel_Input_Register) & DIO_SDIN) { + /* printk("DIO_P_I_R: 0x%x\n", devpriv->stc_readw(dev, DIO_Parallel_Input_Register)); */ + input |= mask; + } + } + + if (data_in) + *data_in = input; + + 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); + } +} + +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); + } + ao_win_out(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: + printk("%s: unhandled register 0x%x in switch.\n", + __func__, reg); + BUG(); + return 0; + break; + } + 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; + struct ni_private *devpriv = dev->private; + 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(bits, M_Offset_G0_Counting_Mode); + break; + case NITIO_G1_CNT_MODE: + ni_writew(bits, M_Offset_G1_Counting_Mode); + break; + case NITIO_G0_GATE2: + ni_writew(bits, M_Offset_G0_Second_Gate); + break; + case NITIO_G1_GATE2: + ni_writew(bits, M_Offset_G1_Second_Gate); + break; + case NITIO_G0_DMA_CFG: + ni_writew(bits, M_Offset_G0_DMA_Config); + break; + case NITIO_G1_DMA_CFG: + ni_writew(bits, M_Offset_G1_DMA_Config); + break; + case NITIO_G0_ABZ: + ni_writew(bits, M_Offset_G0_MSeries_ABZ); + break; + case NITIO_G1_ABZ: + ni_writew(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); + devpriv->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); + devpriv->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; + struct ni_private *devpriv = dev->private; + unsigned stc_register; + + switch (reg) { + /* m-series only registers */ + case NITIO_G0_DMA_STATUS: + return ni_readw(M_Offset_G0_DMA_Status); + case NITIO_G1_DMA_STATUS: + return ni_readw(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 devpriv->stc_readl(dev, stc_register); + + /* 16 bit registers */ + default: + stc_register = ni_gpct_to_stc_register(reg); + return devpriv->stc_readw(dev, stc_register); + break; + } + 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; + + data[0] = devpriv->clock_and_fout & FOUT_Divider_mask; + return 1; +} + +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; + + devpriv->clock_and_fout &= ~FOUT_Enable; + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + devpriv->clock_and_fout &= ~FOUT_Divider_mask; + devpriv->clock_and_fout |= FOUT_Divider(data[0]); + devpriv->clock_and_fout |= FOUT_Enable; + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + return insn->n; +} + +static int ni_set_freq_out_clock(struct comedi_device *dev, + unsigned int clock_source) +{ + struct ni_private *devpriv = dev->private; + + switch (clock_source) { + 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; + } + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + return 3; +} + +static void ni_get_freq_out_clock(struct comedi_device *dev, + unsigned int *clock_source, + unsigned int *clock_period_ns) +{ + struct ni_private *devpriv = dev->private; + + if (devpriv->clock_and_fout & FOUT_Timebase_Select) { + *clock_source = NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC; + *clock_period_ns = TIMEBASE_2_NS; + } else { + *clock_source = NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC; + *clock_period_ns = TIMEBASE_1_NS * 2; + } +} + +static int ni_freq_out_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + return ni_set_freq_out_clock(dev, data[1]); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ni_get_freq_out_clock(dev, &data[1], &data[2]); + return 3; + default: + break; + } + return -EINVAL; +} + +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) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s; + unsigned j; + enum ni_gpct_variant counter_variant; + int ret; + + if (board->n_aochan > MAX_N_AO_CHAN) { + printk("bug! n_aochan > MAX_N_AO_CHAN\n"); + return -EINVAL; + } + + ret = comedi_alloc_subdevices(dev, NI_NUM_SUBDEVICES); + if (ret) + return ret; + + /* analog input subdevice */ + + s = &dev->subdevices[NI_AI_SUBDEV]; + dev->read_subdev = s; + if (board->n_adchan) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_DIFF | SDF_DITHER | SDF_CMD_READ; + if (board->reg_type != ni_reg_611x) + s->subdev_flags |= SDF_GROUND | SDF_COMMON | SDF_OTHER; + if (board->adbits > 16) + s->subdev_flags |= SDF_LSAMPL; + if (board->reg_type & ni_reg_m_series_mask) + s->subdev_flags |= SDF_SOFT_CALIBRATED; + s->n_chan = board->n_adchan; + s->len_chanlist = 512; + s->maxdata = (1 << board->adbits) - 1; + s->range_table = ni_range_lkup[board->gainlkup]; + s->insn_read = &ni_ai_insn_read; + s->insn_config = &ni_ai_insn_config; + 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; +#ifdef PCIDMA + s->async_dma_dir = DMA_FROM_DEVICE; +#endif + } 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 (board->reg_type & ni_reg_m_series_mask) + s->subdev_flags |= SDF_SOFT_CALIBRATED; + s->n_chan = board->n_aochan; + s->maxdata = (1 << board->aobits) - 1; + s->range_table = board->ao_range_table; + s->insn_read = &ni_ao_insn_read; + if (board->reg_type & ni_reg_6xxx_mask) + s->insn_write = &ni_ao_insn_write_671x; + else + s->insn_write = &ni_ao_insn_write; + s->insn_config = &ni_ao_insn_config; +#ifdef PCIDMA + if (board->n_aochan) { + s->async_dma_dir = DMA_TO_DEVICE; +#else + if (board->ao_fifo_depth) { +#endif + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->do_cmd = &ni_ao_cmd; + s->do_cmdtest = &ni_ao_cmdtest; + s->len_chanlist = board->n_aochan; + if ((board->reg_type & ni_reg_m_series_mask) == 0) + s->munge = ni_ao_munge; + } + s->cancel = &ni_ao_reset; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + if ((board->reg_type & ni_reg_67xx_mask)) + init_ao_67xx(dev, s); + + /* digital i/o subdevice */ + + s = &dev->subdevices[NI_DIO_SUBDEV]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->maxdata = 1; + s->io_bits = 0; /* all bits input */ + s->range_table = &range_digital; + s->n_chan = board->num_p0_dio_channels; + if (board->reg_type & ni_reg_m_series_mask) { + s->subdev_flags |= + SDF_LSAMPL | SDF_CMD_WRITE /* | SDF_CMD_READ */; + s->insn_bits = &ni_m_series_dio_insn_bits; + s->insn_config = &ni_m_series_dio_insn_config; + s->do_cmd = &ni_cdio_cmd; + s->do_cmdtest = &ni_cdio_cmdtest; + s->cancel = &ni_cdio_cancel; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->len_chanlist = s->n_chan; + + ni_writel(CDO_Reset_Bit | CDI_Reset_Bit, M_Offset_CDIO_Command); + ni_writel(s->io_bits, M_Offset_DIO_Direction); + } else { + s->insn_bits = &ni_dio_insn_bits; + s->insn_config = &ni_dio_insn_config; + devpriv->dio_control = DIO_Pins_Dir(s->io_bits); + ni_writew(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, + (unsigned long)dev); + 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 -- ai and ao */ + s = &dev->subdevices[NI_CALIBRATION_SUBDEV]; + s->type = COMEDI_SUBD_CALIB; + if (board->reg_type & ni_reg_m_series_mask) { + /* internal PWM analog output used for AI nonlinearity calibration */ + s->subdev_flags = SDF_INTERNAL; + s->insn_config = &ni_m_series_pwm_config; + s->n_chan = 1; + s->maxdata = 0; + ni_writel(0x0, M_Offset_Cal_PWM); + } else if (board->reg_type == ni_reg_6143) { + /* internal PWM analog output used for AI nonlinearity calibration */ + s->subdev_flags = SDF_INTERNAL; + s->insn_config = &ni_6143_pwm_config; + s->n_chan = 1; + s->maxdata = 0; + } else { + s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL; + s->insn_read = &ni_calib_insn_read; + s->insn_write = &ni_calib_insn_write; + caldac_setup(dev, s); + } + + /* EEPROM */ + s = &dev->subdevices[NI_EEPROM_SUBDEV]; + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->maxdata = 0xff; + if (board->reg_type & ni_reg_m_series_mask) { + 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; + } + + /* PFI */ + s = &dev->subdevices[NI_PFI_DIO_SUBDEV]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + if (board->reg_type & ni_reg_m_series_mask) { + unsigned i; + s->n_chan = 16; + ni_writew(s->state, M_Offset_PFI_DO); + for (i = 0; i < NUM_PFI_OUTPUT_SELECT_REGS; ++i) { + ni_writew(devpriv->pfi_output_select_reg[i], + M_Offset_PFI_Output_Select(i + 1)); + } + } else { + s->n_chan = 10; + } + s->maxdata = 1; + if (board->reg_type & ni_reg_m_series_mask) + s->insn_bits = &ni_pfi_insn_bits; + 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 (board->reg_type & ni_reg_67xx_mask) { + 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); + + if (board->reg_type & ni_reg_m_series_mask) + counter_variant = ni_gpct_variant_m_series; + else + counter_variant = ni_gpct_variant_e_series; + devpriv->counter_dev = ni_gpct_device_construct(dev, + &ni_gpct_write_register, + &ni_gpct_read_register, + counter_variant, + NUM_GPCT); + if (!devpriv->counter_dev) + return -ENOMEM; + + /* General purpose counters */ + for (j = 0; j < NUM_GPCT; ++j) { + s = &dev->subdevices[NI_GPCT_SUBDEV(j)]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; + s->n_chan = 3; + if (board->reg_type & ni_reg_m_series_mask) + s->maxdata = 0xffffffff; + else + s->maxdata = 0xffffff; + s->insn_read = ni_tio_insn_read; + s->insn_write = ni_tio_insn_read; + s->insn_config = ni_tio_insn_config; +#ifdef PCIDMA + s->subdev_flags |= SDF_CMD_READ /* | SDF_CMD_WRITE */; + s->do_cmd = &ni_gpct_cmd; + s->len_chanlist = 1; + s->do_cmdtest = ni_tio_cmdtest; + s->cancel = &ni_gpct_cancel; + s->async_dma_dir = DMA_BIDIRECTIONAL; +#endif + s->private = &devpriv->counter_dev->counters[j]; + + devpriv->counter_dev->counters[j].chip_index = 0; + devpriv->counter_dev->counters[j].counter_index = j; + ni_tio_init_counter(&devpriv->counter_dev->counters[j]); + } + + /* Frequency output */ + 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; + + /* ai configuration */ + s = &dev->subdevices[NI_AI_SUBDEV]; + ni_ai_reset(dev, s); + if ((board->reg_type & ni_reg_6xxx_mask) == 0) { + /* BEAM is this needed for PCI-6143 ?? */ + devpriv->clock_and_fout = + Slow_Internal_Time_Divide_By_2 | + Slow_Internal_Timebase | + Clock_To_Board_Divide_By_2 | + Clock_To_Board | + AI_Output_Divide_By_2 | AO_Output_Divide_By_2; + } else { + devpriv->clock_and_fout = + Slow_Internal_Time_Divide_By_2 | + Slow_Internal_Timebase | + Clock_To_Board_Divide_By_2 | Clock_To_Board; + } + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + + /* analog output configuration */ + s = &dev->subdevices[NI_AO_SUBDEV]; + ni_ao_reset(dev, s); + + if (dev->irq) { + devpriv->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 + (dev->irq)) | + Interrupt_B_Output_Select(interrupt_pin + (dev->irq)), + Interrupt_Control_Register); + } + + /* DMA setup */ + ni_writeb(devpriv->ai_ao_select_reg, AI_AO_Select); + ni_writeb(devpriv->g0_g1_select_reg, G0_G1_Select); + + if (board->reg_type & ni_reg_6xxx_mask) { + ni_writeb(0, Magic_611x); + } else if (board->reg_type & ni_reg_m_series_mask) { + int channel; + for (channel = 0; channel < board->n_aochan; ++channel) { + ni_writeb(0xf, M_Offset_AO_Waveform_Order(channel)); + ni_writeb(0x0, + M_Offset_AO_Reference_Attenuation(channel)); + } + ni_writeb(0x0, M_Offset_AO_Calibration); + } + + return 0; +} + +static int ni_8255_callback(int dir, int port, int data, unsigned long arg) +{ + struct comedi_device *dev = (struct comedi_device *)arg; + struct ni_private *devpriv __maybe_unused = dev->private; + + if (dir) { + ni_writeb(data, Port_A + 2 * port); + return 0; + } else { + return ni_readb(Port_A + 2 * port); + } +} + +/* + presents the EEPROM as a subdevice +*/ + +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; +} + +/* + reads bytes out of eeprom +*/ + +static int ni_read_eeprom(struct comedi_device *dev, int addr) +{ + struct ni_private *devpriv __maybe_unused = dev->private; + int bit; + int bitstring; + + bitstring = 0x0300 | ((addr & 0x100) << 3) | (addr & 0xff); + ni_writeb(0x04, Serial_Command); + for (bit = 0x8000; bit; bit >>= 1) { + ni_writeb(0x04 | ((bit & bitstring) ? 0x02 : 0), + Serial_Command); + ni_writeb(0x05 | ((bit & bitstring) ? 0x02 : 0), + Serial_Command); + } + bitstring = 0; + for (bit = 0x80; bit; bit >>= 1) { + ni_writeb(0x04, Serial_Command); + ni_writeb(0x05, Serial_Command); + bitstring |= ((ni_readb(XXX_Status) & PROMOUT) ? bit : 0); + } + ni_writeb(0x00, Serial_Command); + + return bitstring; +} + +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 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 TRIG_ROUND_NEAREST: + up_count = + (data[2] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + up_count = data[2] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + up_count = + (data[2] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + switch (data[3]) { + case TRIG_ROUND_NEAREST: + down_count = + (data[4] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + down_count = data[4] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + down_count = + (data[4] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + 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(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; + break; + case INSN_CONFIG_GET_PWM_OUTPUT: + return ni_get_pwm_config(dev, data); + break; + default: + return -EINVAL; + break; + } + 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 TRIG_ROUND_NEAREST: + up_count = + (data[2] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + up_count = data[2] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + up_count = + (data[2] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + switch (data[3]) { + case TRIG_ROUND_NEAREST: + down_count = + (data[4] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + down_count = data[4] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + down_count = + (data[4] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + 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(up_count, Calibration_HighTime_6143); + devpriv->pwm_up_count = up_count; + ni_writel(down_count, Calibration_LowTime_6143); + devpriv->pwm_down_count = down_count; + return 5; + break; + case INSN_CONFIG_GET_PWM_OUTPUT: + return ni_get_pwm_config(dev, data); + default: + return -EINVAL; + break; + } + return 0; +} + +static void ni_write_caldac(struct comedi_device *dev, int addr, int val); +/* + calibration subdevice +*/ +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 int pack_mb88341(int addr, int val, int *bitstring); +static int pack_dac8800(int addr, int val, int *bitstring); +static int pack_dac8043(int addr, int val, int *bitstring); +static int pack_ad8522(int addr, int val, int *bitstring); +static int pack_ad8804(int addr, int val, int *bitstring); +static int pack_ad8842(int addr, int val, int *bitstring); + +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 caldac_setup(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct ni_board_struct *board = comedi_board(dev); + 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) + printk("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 void ni_write_caldac(struct comedi_device *dev, int addr, int val) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + unsigned int loadbit = 0, bits = 0, bit, bitstring = 0; + int i; + int type; + + /* printk("ni_write_caldac: chan=%d val=%d\n",addr,val); */ + 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); + /* printk("caldac: using i=%d addr=%d %x\n",i,addr,bitstring); */ + break; + } + addr -= caldacs[type].n_chans; + } + + for (bit = 1 << (bits - 1); bit; bit >>= 1) { + ni_writeb(((bit & bitstring) ? 0x02 : 0), Serial_Command); + udelay(1); + ni_writeb(1 | ((bit & bitstring) ? 0x02 : 0), Serial_Command); + udelay(1); + } + ni_writeb(loadbit, Serial_Command); + udelay(1); + ni_writeb(0, Serial_Command); +} + +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; +} + +#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; + devpriv->stc_writew(dev, devpriv->gpct_command[chan], + G_Command_Register(chan)); + + devpriv->gpct_command[chan] |= G_Save_Trace; + devpriv->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 = devpriv->stc_readw(dev, G_Save_Register_High(chan)); + lo = devpriv->stc_readw(dev, G_Save_Register_Low(chan)); + hi2 = devpriv->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; + + /* printk("GPCT_Reset..."); */ + devpriv->gpct_cur_operation[chan] = GPCT_RESET; + + switch (chan) { + case 0: + devpriv->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; + devpriv->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; + devpriv->stc_writew(dev, devpriv->an_trig_etc_reg, + Analog_Trigger_Etc_Register); + break; + case 1: + devpriv->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; + devpriv->stc_writew(dev, temp_ack_reg, + Interrupt_B_Ack_Register); + + devpriv->an_trig_etc_reg |= GPFO_1_Output_Enable; + devpriv->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; + + devpriv->stc_writew(dev, devpriv->gpct_mode[chan], + G_Mode_Register(chan)); + devpriv->stc_writew(dev, devpriv->gpct_input_select[chan], + G_Input_Select_Register(chan)); + devpriv->stc_writew(dev, 0, G_Autoincrement_Register(chan)); + + /* printk("exit GPCT_Reset\n"); */ +} + +#endif + +#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) { + comedi_error(dev, + "no dma channel available for use by counter"); + return retval; + } + ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL, NULL); + ni_e_series_enable_second_irq(dev, counter->counter_index, 1); + + return ni_tio_cmd(dev, s); +} +#endif + +#ifdef PCIDMA +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 + +/* + * + * Programmable Function Inputs + * + */ + +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(devpriv->pfi_output_select_reg[array_offset], + M_Offset_PFI_Output_Select(pfi_reg_index)); + return 2; +} + +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 int ni_set_pfi_routing(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + const struct ni_board_struct *board = comedi_board(dev); + + if (board->reg_type & ni_reg_m_series_mask) + return ni_m_series_set_pfi_routing(dev, chan, source); + else + return ni_old_set_pfi_routing(dev, chan, source); +} + +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 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; + break; + case 1: + return NI_PFI_OUTPUT_AI_START2; + break; + case 2: + return NI_PFI_OUTPUT_AI_CONVERT; + break; + case 3: + return NI_PFI_OUTPUT_G_SRC1; + break; + case 4: + return NI_PFI_OUTPUT_G_GATE1; + break; + case 5: + return NI_PFI_OUTPUT_AO_UPDATE_N; + break; + case 6: + return NI_PFI_OUTPUT_AO_START1; + break; + case 7: + return NI_PFI_OUTPUT_AI_START_PULSE; + break; + case 8: + return NI_PFI_OUTPUT_G_SRC0; + break; + case 9: + return NI_PFI_OUTPUT_G_GATE0; + break; + default: + printk("%s: bug, unhandled case in switch.\n", __func__); + break; + } + return 0; +} + +static unsigned ni_get_pfi_routing(struct comedi_device *dev, unsigned chan) +{ + const struct ni_board_struct *board = comedi_board(dev); + + if (board->reg_type & ni_reg_m_series_mask) + return ni_m_series_get_pfi_routing(dev, chan); + else + return ni_old_get_pfi_routing(dev, chan); +} + +static int ni_config_filter(struct comedi_device *dev, unsigned pfi_channel, + enum ni_pfi_filter_select filter) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv __maybe_unused = dev->private; + unsigned bits; + + if ((board->reg_type & ni_reg_m_series_mask) == 0) + return -ENOTSUPP; + bits = ni_readl(M_Offset_PFI_Filter); + bits &= ~MSeries_PFI_Filter_Select_Mask(pfi_channel); + bits |= MSeries_PFI_Filter_Select_Bits(pfi_channel, filter); + ni_writel(bits, M_Offset_PFI_Filter); + return 0; +} + +static int ni_pfi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv __maybe_unused = dev->private; + + if (!(board->reg_type & ni_reg_m_series_mask)) + return -ENOTSUPP; + + if (comedi_dio_update_state(s, data)) + ni_writew(s->state, M_Offset_PFI_DO); + + data[1] = ni_readw(M_Offset_PFI_DI); + + return insn->n; +} + +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; + break; + case INSN_CONFIG_SET_ROUTING: + return ni_set_pfi_routing(dev, chan, data[1]); + break; + 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]); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * + * NI RTSI Bus Functions + * + */ +static void ni_rtsi_init(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(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) + printk("ni_set_master_clock failed, bug?"); + /* 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); + devpriv->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 (board->reg_type & ni_reg_m_series_mask) + devpriv->rtsi_trig_b_output_reg |= + RTSI_Trig_Output_Bits(7, NI_RTSI_OUTPUT_RTSI_OSC); + devpriv->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 +* devpriv->stc_writew(dev, 0x0000, RTSI_Board_Register); +*/ +} + +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; +} + +/* 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) { + printk("%s: bug, failed to find pll parameters\n", __func__); + 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 inline unsigned num_configurable_rtsi_channels(struct comedi_device *dev) +{ + const struct ni_board_struct *board = comedi_board(dev); + + if (board->reg_type & ni_reg_m_series_mask) + return 8; + else + return 7; +} + +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) { + printk + ("%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; + devpriv->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; + retval = ni_mseries_get_pll_parameters(period_ns, &freq_divider, + &freq_multiplier, + &devpriv->clock_ns); + if (retval < 0) + return retval; + break; + case NI_MIO_PLL_PXI10_CLOCK: + /* pxi clock is 10MHz */ + devpriv->clock_and_fout2 |= + MSeries_PLL_In_Source_Select_PXI_Clock10; + retval = ni_mseries_get_pll_parameters(period_ns, &freq_divider, + &freq_multiplier, + &devpriv->clock_ns); + if (retval < 0) + return retval; + 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; + retval = ni_mseries_get_pll_parameters(period_ns, + &freq_divider, + &freq_multiplier, + &devpriv-> + clock_ns); + if (retval < 0) + return retval; + } + break; + } + ni_writew(devpriv->clock_and_fout2, M_Offset_Clock_and_Fout2); + pll_control_bits |= + MSeries_PLL_Divisor_Bits(freq_divider) | + MSeries_PLL_Multiplier_Bits(freq_multiplier); + + /* printk("using divider=%i, multiplier=%i for PLL. pll_control_bits = 0x%x\n", + * freq_divider, freq_multiplier, pll_control_bits); */ + /* printk("clock_ns=%d\n", devpriv->clock_ns); */ + ni_writew(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(M_Offset_PLL_Status) & MSeries_PLL_Locked_Bit) + break; + udelay(1); + } + if (i == timeout) { + printk + ("%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) +{ + const struct ni_board_struct *board = comedi_board(dev); + struct ni_private *devpriv = dev->private; + + if (source == NI_MIO_INTERNAL_CLOCK) { + devpriv->rtsi_trig_direction_reg &= ~Use_RTSI_Clock_Bit; + devpriv->stc_writew(dev, devpriv->rtsi_trig_direction_reg, + RTSI_Trig_Direction_Register); + devpriv->clock_ns = TIMEBASE_1_NS; + if (board->reg_type & ni_reg_m_series_mask) { + devpriv->clock_and_fout2 &= + ~(MSeries_Timebase1_Select_Bit | + MSeries_Timebase3_Select_Bit); + ni_writew(devpriv->clock_and_fout2, + M_Offset_Clock_and_Fout2); + ni_writew(0, M_Offset_PLL_Control); + } + devpriv->clock_source = source; + } else { + if (board->reg_type & ni_reg_m_series_mask) { + 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; + devpriv->stc_writew(dev, + devpriv-> + rtsi_trig_direction_reg, + RTSI_Trig_Direction_Register); + if (period_ns == 0) { + printk + ("%s: we don't handle an unspecified clock period correctly yet, returning error.\n", + __func__); + return -EINVAL; + } else { + devpriv->clock_ns = period_ns; + } + devpriv->clock_source = source; + } else + return -EINVAL; + } + } + return 3; +} + +static int ni_valid_rtsi_output_source(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + const struct ni_board_struct *board = comedi_board(dev); + + if (chan >= num_configurable_rtsi_channels(dev)) { + if (chan == old_RTSI_clock_channel) { + if (source == NI_RTSI_OUTPUT_RTSI_OSC) + return 1; + else { + printk + ("%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; + break; + case NI_RTSI_OUTPUT_RTSI_OSC: + if (board->reg_type & ni_reg_m_series_mask) + return 1; + else + return 0; + break; + default: + return 0; + break; + } +} + +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); + devpriv->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); + devpriv->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; + printk("%s: bug! should never get here?\n", __func__); + return 0; + } +} + +static int ni_rtsi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct ni_board_struct *board = comedi_board(dev); + 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, + (board->reg_type & ni_reg_m_series_mask) != 0); + } else if (chan == old_RTSI_clock_channel) { + devpriv->rtsi_trig_direction_reg |= + Drive_RTSI_Clock_Bit; + } + devpriv->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, + (board->reg_type & ni_reg_m_series_mask) != 0); + } else if (chan == old_RTSI_clock_channel) { + devpriv->rtsi_trig_direction_reg &= + ~Drive_RTSI_Clock_Bit; + } + devpriv->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, + (board->reg_type & ni_reg_m_series_mask) != 0)) + ? 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; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + return ni_set_master_clock(dev, data[1], data[2]); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + data[1] = devpriv->clock_source; + data[2] = devpriv->clock_ns; + return 3; + break; + case INSN_CONFIG_SET_ROUTING: + return ni_set_rtsi_routing(dev, chan, data[1]); + break; + case INSN_CONFIG_GET_ROUTING: + data[1] = ni_get_rtsi_routing(dev, chan); + return 2; + break; + default: + return -EINVAL; + break; + } + return 1; +} + +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; + } +/* printk("looped %i times waiting for idle\n", i); */ + if (i == timeout) { + printk("%s: %s: timeout\n", __FILE__, __func__); + 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); + } +/* printk("looped %i times writing command to cs5529\n", i); */ + if (i == timeout) + comedi_error(dev, "possible problem - never saw adc go busy?"); +} + +/* write to cs5529 register */ +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)) + comedi_error(dev, "time or signal in cs5529_config_write()"); +} + +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) { + comedi_error(dev, + "timeout or signal in cs5529_do_conversion()"); + return -ETIME; + } + status = ni_ao_win_inw(dev, CAL_ADC_Status_67xx); + if (status & CSS_OSC_DETECT) { + printk + ("ni_mio_common: cs5529 conversion error, status CSS_OSC_DETECT\n"); + return -EIO; + } + if (status & CSS_OVERRANGE) { + printk + ("ni_mio_common: 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 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)) + comedi_error(dev, "timeout or signal in init_cs5529()\n"); +#endif + return 0; +} 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 00000000000..de421486b75 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_mio_cs.c @@ -0,0 +1,310 @@ +/* + 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 "../comedidev.h" + +#include <linux/delay.h> + +#include "ni_stc.h" +#include "8255.h" + +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#define ATMIO 1 +#undef PCIMIO + +/* + * AT specific setup + */ + +#define NI_SIZE 0x20 + +#define MAX_N_CALDACS 32 + +static const struct ni_board_struct ni_boards[] = { + { + .device_id = 0x010d, + .name = "DAQCard-ai-16xe-50", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_8, + .ai_speed = 5000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043 }, + }, { + .device_id = 0x010c, + .name = "DAQCard-ai-16e-4", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_16, + .ai_speed = 4000, + .num_p0_dio_channels = 8, + .caldac = { mb88341 }, /* verified */ + }, { + .device_id = 0x02c4, + .name = "DAQCard-6062E", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_bipolar10, + .ao_speed = 1176, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, /* verified */ + }, { + /* specs incorrect! */ + .device_id = 0x075e, + .name = "DAQCard-6024E", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, { + /* specs incorrect! */ + .device_id = 0x0245, + .name = "DAQCard-6036E", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 1024, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 16, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, +#if 0 + { + .device_id = 0x0000, /* unknown */ + .name = "DAQCard-6715", + .n_aochan = 8, + .aobits = 12, + .ao_671x = 8192, + .num_p0_dio_channels = 8, + .caldac = { mb88341, mb88341 }, + }, +#endif +}; + +#define interrupt_pin(a) 0 + +#define IRQ_POLARITY 1 + +struct ni_private { + + struct pcmcia_device *link; + +NI_PRIVATE_COMMON}; + +/* How we access registers */ + +#define ni_writel(a, b) (outl((a), (b)+dev->iobase)) +#define ni_readl(a) (inl((a)+dev->iobase)) +#define ni_writew(a, b) (outw((a), (b)+dev->iobase)) +#define ni_readw(a) (inw((a)+dev->iobase)) +#define ni_writeb(a, b) (outb((a), (b)+dev->iobase)) +#define ni_readb(a) (inb((a)+dev->iobase)) + +/* How we access windowed registers */ + +/* We automatically take advantage of STC registers that can be + * read/written directly in the I/O space of the board. The + * DAQCard devices map the low 8 STC registers to iobase+addr*2. */ + +static void mio_cs_win_out(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); + if (addr < 8) { + ni_writew(data, addr * 2); + } else { + ni_writew(addr, Window_Address); + ni_writew(data, Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static uint16_t mio_cs_win_in(struct comedi_device *dev, int addr) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + uint16_t ret; + + spin_lock_irqsave(&devpriv->window_lock, flags); + if (addr < 8) { + ret = ni_readw(addr * 2); + } else { + ni_writew(addr, Window_Address); + ret = ni_readw(Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); + + return ret; +} + +#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; + struct ni_private *devpriv; + 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; + + devpriv = dev->private; + devpriv->stc_writew = mio_cs_win_out; + devpriv->stc_readw = mio_cs_win_in; + devpriv->stc_writel = win_out2; + devpriv->stc_readl = win_in2; + + return ni_E_init(dev); +} + +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 00000000000..5fc74d6ff6a --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_pcidio.c @@ -0,0 +1,1088 @@ +/* + 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 "../comedidev.h" + +#include "comedi_fc.h" +#include "mite.h" + +#define PCI_DIO_SIZE 4096 +#define PCI_MITE_SIZE 4096 + +/* 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 "ni6534a.bin" +#define FW_PCI_6534_SCARAB_DI "niscrb01.bin" +#define FW_PCI_6534_SCARAB_DO "niscrb02.bin" +MODULE_FIRMWARE(FW_PCI_6534_MAIN); +MODULE_FIRMWARE(FW_PCI_6534_SCARAB_DI); +MODULE_FIRMWARE(FW_PCI_6534_SCARAB_DO); + +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_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int ni_pcidio_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_pcidio_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum); +static int ni_pcidio_ns_to_timer(int *nanosec, int round_mode); +static int setup_mite_dma(struct comedi_device *dev, + struct comedi_subdevice *s); + +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 == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, "failed to reserve mite dma channel."); + 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), + devpriv->mite->daq_io_addr + 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), + devpriv->mite->daq_io_addr + DMA_Line_Control_Group1); + mmiowb(); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +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 = s->async->buf_write_count - s->async->buf_read_count; + 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; + + /* int i, j; */ + unsigned int auxdata = 0; + unsigned short data1 = 0; + unsigned short data2 = 0; + 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(devpriv->mite->daq_io_addr + + Interrupt_And_Window_Status); + flags = readb(devpriv->mite->daq_io_addr + 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_EOA | 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, + devpriv->mite->daq_io_addr + + 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, + devpriv->mite->daq_io_addr + + Master_DMA_And_Interrupt_Control + ); + goto out; + } + auxdata = + readl(devpriv->mite->daq_io_addr + + Group_1_FIFO); + data1 = auxdata & 0xffff; + data2 = (auxdata & 0xffff0000) >> 16; + comedi_buf_put(s, data1); + comedi_buf_put(s, data2); + flags = readb(devpriv->mite->daq_io_addr + + Group_1_Flags); + } + async->events |= COMEDI_CB_BLOCK; + } + + if (flags & CountExpired) { + writeb(ClearExpired, + devpriv->mite->daq_io_addr + + Group_1_Second_Clear); + async->events |= COMEDI_CB_EOA; + + writeb(0x00, devpriv->mite->daq_io_addr + OpMode); + break; + } else if (flags & Waited) { + writeb(ClearWaited, + devpriv->mite->daq_io_addr + + Group_1_First_Clear); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + break; + } else if (flags & PrimaryTC) { + writeb(ClearPrimaryTC, + devpriv->mite->daq_io_addr + + Group_1_First_Clear); + async->events |= COMEDI_CB_EOA; + } else if (flags & SecondaryTC) { + writeb(ClearSecondaryTC, + devpriv->mite->daq_io_addr + + Group_1_First_Clear); + async->events |= COMEDI_CB_EOA; + } + + flags = readb(devpriv->mite->daq_io_addr + Group_1_Flags); + status = readb(devpriv->mite->daq_io_addr + + Interrupt_And_Window_Status); + } + +out: + cfc_handle_events(dev, s); +#if 0 + if (!tag) { + writeb(0x03, + devpriv->mite->daq_io_addr + + 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) +{ + struct nidio96_private *devpriv = dev->private; + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + writel(s->io_bits, devpriv->mite->daq_io_addr + 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) +{ + struct nidio96_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) + writel(s->state, devpriv->mite->daq_io_addr + Port_IO(0)); + + data[1] = readl(devpriv->mite->daq_io_addr + Port_IO(0)); + + return insn->n; +} + +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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SPEED (TIMER_BASE) /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* no limit */ + } else { /* TRIG_NONE */ + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + return 0; +} + +static int ni_pcidio_ns_to_timer(int *nanosec, int round_mode) +{ + int divider, base; + + base = TIMER_BASE; + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + + *nanosec = base * divider; + return divider; +} + +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, devpriv->mite->daq_io_addr + Port_Pin_Directions(0)); + + if (1) { + /* enable fifos A B C D */ + writeb(0x0f, devpriv->mite->daq_io_addr + Data_Path); + + /* set transfer width a 32 bits */ + writeb(TransferWidth(0) | TransferLength(0), + devpriv->mite->daq_io_addr + Transfer_Size_Control); + } else { + writeb(0x03, devpriv->mite->daq_io_addr + Data_Path); + writeb(TransferWidth(3) | TransferLength(0), + devpriv->mite->daq_io_addr + Transfer_Size_Control); + } + + /* protocol configuration */ + if (cmd->scan_begin_src == TRIG_TIMER) { + /* page 4-5, "input with internal REQs" */ + writeb(0, devpriv->mite->daq_io_addr + OpMode); + writeb(0x00, devpriv->mite->daq_io_addr + ClockReg); + writeb(1, devpriv->mite->daq_io_addr + Sequence); + writeb(0x04, devpriv->mite->daq_io_addr + ReqReg); + writeb(4, devpriv->mite->daq_io_addr + BlockMode); + writeb(3, devpriv->mite->daq_io_addr + LinePolarities); + writeb(0xc0, devpriv->mite->daq_io_addr + AckSer); + writel(ni_pcidio_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_NEAREST), + devpriv->mite->daq_io_addr + StartDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqNotDelay); + writeb(1, devpriv->mite->daq_io_addr + AckDelay); + writeb(0x0b, devpriv->mite->daq_io_addr + AckNotDelay); + writeb(0x01, devpriv->mite->daq_io_addr + Data1Delay); + /* manual, page 4-5: ClockSpeed comment is incorrectly listed + * on DAQOptions */ + writew(0, devpriv->mite->daq_io_addr + ClockSpeed); + writeb(0, devpriv->mite->daq_io_addr + DAQOptions); + } else { + /* TRIG_EXT */ + /* page 4-5, "input with external REQs" */ + writeb(0, devpriv->mite->daq_io_addr + OpMode); + writeb(0x00, devpriv->mite->daq_io_addr + ClockReg); + writeb(0, devpriv->mite->daq_io_addr + Sequence); + writeb(0x00, devpriv->mite->daq_io_addr + ReqReg); + writeb(4, devpriv->mite->daq_io_addr + BlockMode); + if (!(cmd->scan_begin_arg & CR_INVERT)) { + /* Leading Edge pulse mode */ + writeb(0, devpriv->mite->daq_io_addr + LinePolarities); + } else { + /* Trailing Edge pulse mode */ + writeb(2, devpriv->mite->daq_io_addr + LinePolarities); + } + writeb(0x00, devpriv->mite->daq_io_addr + AckSer); + writel(1, devpriv->mite->daq_io_addr + StartDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqNotDelay); + writeb(1, devpriv->mite->daq_io_addr + AckDelay); + writeb(0x0C, devpriv->mite->daq_io_addr + AckNotDelay); + writeb(0x10, devpriv->mite->daq_io_addr + Data1Delay); + writew(0, devpriv->mite->daq_io_addr + ClockSpeed); + writeb(0x60, devpriv->mite->daq_io_addr + DAQOptions); + } + + if (cmd->stop_src == TRIG_COUNT) { + writel(cmd->stop_arg, + devpriv->mite->daq_io_addr + Transfer_Count); + } else { + /* XXX */ + } + +#ifdef USE_DMA + writeb(ClearPrimaryTC | ClearSecondaryTC, + devpriv->mite->daq_io_addr + Group_1_First_Clear); + + { + int retval = setup_mite_dma(dev, s); + if (retval) + return retval; + } +#else + writeb(0x00, devpriv->mite->daq_io_addr + DMA_Line_Control_Group1); +#endif + writeb(0x00, devpriv->mite->daq_io_addr + DMA_Line_Control_Group2); + + /* clear and enable interrupts */ + writeb(0xff, devpriv->mite->daq_io_addr + Group_1_First_Clear); + /* writeb(ClearExpired, + devpriv->mite->daq_io_addr+Group_1_Second_Clear); */ + + writeb(IntEn, devpriv->mite->daq_io_addr + Interrupt_Control); + writeb(0x03, + devpriv->mite->daq_io_addr + 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, + devpriv->mite->daq_io_addr + OpMode); + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + s->async->inttrig = ni_pcidio_inttrig; + } + + return 0; +} + +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_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, devpriv->mite->daq_io_addr + OpMode); + s->async->inttrig = NULL; + + return 1; +} + +static int ni_pcidio_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct nidio96_private *devpriv = dev->private; + + writeb(0x00, + devpriv->mite->daq_io_addr + 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, unsigned long new_size) +{ + 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) +{ + struct nidio96_private *devpriv = dev->private; + static const int timeout = 1000; + int fpga_index = context; + int i; + size_t j; + + writew(0x80 | fpga_index, + devpriv->mite->daq_io_addr + Firmware_Control_Register); + writew(0xc0 | fpga_index, + devpriv->mite->daq_io_addr + Firmware_Control_Register); + for (i = 0; + (readw(devpriv->mite->daq_io_addr + + 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, + devpriv->mite->daq_io_addr + Firmware_Control_Register); + for (i = 0; + readw(devpriv->mite->daq_io_addr + 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, + devpriv->mite->daq_io_addr + Firmware_Data_Register); + for (i = 0; + (readw(devpriv->mite->daq_io_addr + + 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, devpriv->mite->daq_io_addr + 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) +{ + struct nidio96_private *devpriv = dev->private; + int ret; + int i; + + writew(0x0, devpriv->mite->daq_io_addr + Firmware_Control_Register); + for (i = 0; i < 3; ++i) { + ret = pci_6534_reset_fpga(dev, i); + if (ret < 0) + break; + } + writew(0x0, devpriv->mite->daq_io_addr + Firmware_Mask_Register); + return ret; +} + +static void pci_6534_init_main_fpga(struct comedi_device *dev) +{ + struct nidio96_private *devpriv = dev->private; + + writel(0, devpriv->mite->daq_io_addr + FPGA_Control1_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_Control2_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_SCALS_Counter_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_SCAMS_Counter_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_SCBLS_Counter_Register); + writel(0, devpriv->mite->daq_io_addr + 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) +{ + struct nidio96_private *devpriv = dev->private; + void __iomem *daq_mmio = devpriv->mite->daq_io_addr; + + writel(0, daq_mmio + Port_IO(0)); + writel(0, daq_mmio + Port_Pin_Directions(0)); + writel(0, daq_mmio + Port_Pin_Mask(0)); + + /* disable interrupts on board */ + writeb(0, daq_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(devpriv->mite); + if (ret < 0) { + dev_warn(dev->class_dev, "error setting up mite\n"); + return ret; + } + + devpriv->di_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->di_mite_ring == NULL) + 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(devpriv->mite->daq_io_addr + 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 = mite_irq(devpriv->mite); + 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; + } + if (devpriv->mite) { + mite_unsetup(devpriv->mite); + mite_free(devpriv->mite); + } + } + 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 00000000000..89300dc78e3 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_pcimio.c @@ -0,0 +1,1706 @@ +/* + 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 "../comedidev.h" + +#include <asm/byteorder.h> + +#include "ni_stc.h" +#include "mite.h" + +#define PCIDMA + +#define PCIMIO 1 +#undef ATMIO + +#define MAX_N_CALDACS (16+16+2) + +#define DRV_NAME "ni_pcimio" + +/* 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, + .adbits = 16, + .ai_fifo_depth = 2048, + .alwaysdither = 1, + .gainlkup = ai_gain_8, + .ai_speed = 50000, + .n_aochan = 2, + .aobits = 12, + .ao_range_table = &range_bipolar10, + .ao_speed = 50000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043 }, + }, + [BOARD_PCIMIO_16XE_10] = { + .name = "pci-mio-16xe-10", /* aka pci-6030E */ + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6014] = { + .name = "pci-6014", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 16, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6030E] = { + .name = "pxi-6030e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCIMIO_16E_1] = { + .name = "pci-mio-16e-1", /* aka pci-6070e */ + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = { mb88341 }, + }, + [BOARD_PCIMIO_16E_4] = { + .name = "pci-mio-16e-4", /* aka pci-6040e */ + .n_adchan = 16, + .adbits = 12, + .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, + .aobits = 12, + .ao_fifo_depth = 512, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, /* doc says mb88341 */ + }, + [BOARD_PXI6040E] = { + .name = "pxi-6040e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 512, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = { mb88341 }, + }, + [BOARD_PCI6031E] = { + .name = "pci-6031e", + .n_adchan = 64, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6032E] = { + .name = "pci-6032e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6033E] = { + .name = "pci-6033e", + .n_adchan = 64, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6071E] = { + .name = "pci-6071e", + .n_adchan = 64, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6023E] = { + .name = "pci-6023e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, /* manual is wrong */ + }, + [BOARD_PCI6024E] = { + .name = "pci-6024e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, /* manual is wrong */ + }, + [BOARD_PCI6025E] = { + .name = "pci-6025e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, /* manual is wrong */ + .has_8255 = 1, + }, + [BOARD_PXI6025E] = { + .name = "pxi-6025e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, /* manual is wrong */ + .has_8255 = 1, + }, + [BOARD_PCI6034E] = { + .name = "pci-6034e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6035E] = { + .name = "pci-6035e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6052E] = { + .name = "pci-6052e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 3000, + .n_aochan = 2, + .aobits = 16, + .ao_unipolar = 1, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 3000, + .num_p0_dio_channels = 8, + /* manual is wrong */ + .caldac = { ad8804_debug, ad8804_debug, ad8522 }, + }, + [BOARD_PCI6110] = { + .name = "pci-6110", + .n_adchan = 4, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_611x, + .ai_speed = 200, + .n_aochan = 2, + .aobits = 16, + .reg_type = ni_reg_611x, + .ao_range_table = &range_bipolar10, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .num_p0_dio_channels = 8, + .caldac = { ad8804, ad8804 }, + }, + [BOARD_PCI6111] = { + .name = "pci-6111", + .n_adchan = 2, + .adbits = 12, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_611x, + .ai_speed = 200, + .n_aochan = 2, + .aobits = 16, + .reg_type = ni_reg_611x, + .ao_range_table = &range_bipolar10, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .num_p0_dio_channels = 8, + .caldac = { ad8804, ad8804 }, + }, +#if 0 + /* The 6115 boards probably need their own driver */ + [BOARD_PCI6115] = { /* .device_id = 0x2ed0, */ + .name = "pci-6115", + .n_adchan = 4, + .adbits = 12, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_611x, + .ai_speed = 100, + .n_aochan = 2, + .aobits = 16, + .ao_671x = 1, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .num_p0_dio_channels = 8, + .reg_611x = 1, + /* XXX */ + .caldac = { ad8804_debug, ad8804_debug, ad8804_debug }, + }, +#endif +#if 0 + [BOARD_PXI6115] = { /* .device_id = ????, */ + .name = "pxi-6115", + .n_adchan = 4, + .adbits = 12, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_611x, + .ai_speed = 100, + .n_aochan = 2, + .aobits = 16, + .ao_671x = 1, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .reg_611x = 1, + .num_p0_dio_channels = 8, + /* XXX */ + .caldac = { ad8804_debug, ad8804_debug, ad8804_debug }, + }, +#endif + [BOARD_PCI6711] = { + .name = "pci-6711", + .n_aochan = 4, + .aobits = 12, + /* data sheet says 8192, but fifo really holds 16384 samples */ + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6711] = { + .name = "pxi-6711", + .n_aochan = 4, + .aobits = 12, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6713] = { + .name = "pci-6713", + .n_aochan = 8, + .aobits = 12, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6713] = { + .name = "pxi-6713", + .n_aochan = 8, + .aobits = 12, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PCI6731] = { + .name = "pci-6731", + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8192, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, +#if 0 + [BOARD_PXI6731] = { /* .device_id = ????, */ + .name = "pxi-6731", + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8192, + .ao_range_table = &range_bipolar10, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, +#endif + [BOARD_PCI6733] = { + .name = "pci-6733", + .n_aochan = 8, + .aobits = 16, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6733] = { + .name = "pxi-6733", + .n_aochan = 8, + .aobits = 16, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6071E] = { + .name = "pxi-6071e", + .n_adchan = 64, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6070E] = { + .name = "pxi-6070e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6052E] = { + .name = "pxi-6052e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 3000, + .n_aochan = 2, + .aobits = 16, + .ao_unipolar = 1, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 3000, + .num_p0_dio_channels = 8, + .caldac = { mb88341, mb88341, ad8522 }, + }, + [BOARD_PXI6031E] = { + .name = "pxi-6031e", + .n_adchan = 64, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6036E] = { + .name = "pci-6036e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 16, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6220] = { + .name = "pci-6220", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, /* FIXME: guess */ + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_622x, + .caldac = { caldac_none }, + }, + [BOARD_PCI6221] = { + .name = "pci-6221", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PCI6221_37PIN] = { + .name = "pci-6221_37pin", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PCI6224] = { + .name = "pci-6224", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .reg_type = ni_reg_622x, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PXI6224] = { + .name = "pxi-6224", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .reg_type = ni_reg_622x, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCI6225] = { + .name = "pci-6225", + .n_adchan = 80, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PXI6225] = { + .name = "pxi-6225", + .n_adchan = 80, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCI6229] = { + .name = "pci-6229", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCI6250] = { + .name = "pci-6250", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .reg_type = ni_reg_625x, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PCI6251] = { + .name = "pci-6251", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PCIE6251] = { + .name = "pcie-6251", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PXIE6251] = { + .name = "pxie-6251", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PCI6254] = { + .name = "pci-6254", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .reg_type = ni_reg_625x, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCI6259] = { + .name = "pci-6259", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCIE6259] = { + .name = "pcie-6259", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCI6280] = { + .name = "pci-6280", + .n_adchan = 16, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .ao_fifo_depth = 8191, + .reg_type = ni_reg_628x, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PCI6281] = { + .name = "pci-6281", + .n_adchan = 16, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_unipolar = 1, + .ao_speed = 350, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PXI6281] = { + .name = "pxi-6281", + .n_adchan = 16, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_unipolar = 1, + .ao_speed = 350, + .num_p0_dio_channels = 8, + .caldac = { caldac_none }, + }, + [BOARD_PCI6284] = { + .name = "pci-6284", + .n_adchan = 32, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .reg_type = ni_reg_628x, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCI6289] = { + .name = "pci-6289", + .n_adchan = 32, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_unipolar = 1, + .ao_speed = 350, + .num_p0_dio_channels = 32, + .caldac = { caldac_none }, + }, + [BOARD_PCI6143] = { + .name = "pci-6143", + .n_adchan = 8, + .adbits = 16, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_6143, + .ai_speed = 4000, + .reg_type = ni_reg_6143, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6143] = { + .name = "pxi-6143", + .n_adchan = 8, + .adbits = 16, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_6143, + .ai_speed = 4000, + .reg_type = ni_reg_6143, + .num_p0_dio_channels = 8, + .caldac = { ad8804_debug, ad8804_debug }, + }, +}; + +struct ni_private { +NI_PRIVATE_COMMON}; + +/* How we access registers */ + +#define ni_writel(a, b) (writel((a), devpriv->mite->daq_io_addr + (b))) +#define ni_readl(a) (readl(devpriv->mite->daq_io_addr + (a))) +#define ni_writew(a, b) (writew((a), devpriv->mite->daq_io_addr + (b))) +#define ni_readw(a) (readw(devpriv->mite->daq_io_addr + (a))) +#define ni_writeb(a, b) (writeb((a), devpriv->mite->daq_io_addr + (b))) +#define ni_readb(a) (readb(devpriv->mite->daq_io_addr + (a))) + +/* How we access STC registers */ + +/* We automatically take advantage of STC registers that can be + * read/written directly in the I/O space of the board. Most + * PCIMIO devices map the low 8 STC registers to iobase+addr*2. + * The 611x devices map the write registers to iobase+addr*2, and + * the read registers to iobase+(addr-1)*2. */ +/* However, the 611x boards still aren't working, so I'm disabling + * non-windowed STC access temporarily */ + +static void e_series_win_out(struct comedi_device *dev, uint16_t data, int reg) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(reg, Window_Address); + ni_writew(data, Window_Data); + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static uint16_t e_series_win_in(struct comedi_device *dev, int reg) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + uint16_t ret; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(reg, Window_Address); + ret = ni_readw(Window_Data); + spin_unlock_irqrestore(&devpriv->window_lock, flags); + + return ret; +} + +static void m_series_stc_writew(struct comedi_device *dev, uint16_t data, + int reg) +{ + struct ni_private *devpriv = dev->private; + 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 actually a 32 bit register on m series boards */ + ni_writel(data, M_Offset_AI_SI2_Load_A); + return; + break; + case AI_SI2_Load_B_Register: + /* this is actually a 32 bit register on m series boards */ + ni_writel(data, M_Offset_AI_SI2_Load_B); + return; + break; + 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; + break; + 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); + BUG(); + return; + break; + } + ni_writew(data, offset); +} + +static uint16_t m_series_stc_readw(struct comedi_device *dev, int reg) +{ + struct ni_private *devpriv = dev->private; + 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(M_Offset_SCXI_Serial_Data_In); + break; + 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); + BUG(); + return 0; + break; + } + return ni_readw(offset); +} + +static void m_series_stc_writel(struct comedi_device *dev, uint32_t data, + int reg) +{ + struct ni_private *devpriv = dev->private; + 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); + BUG(); + return; + break; + } + ni_writel(data, offset); +} + +static uint32_t m_series_stc_readl(struct comedi_device *dev, int reg) +{ + struct ni_private *devpriv = dev->private; + 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); + BUG(); + return 0; + break; + } + return ni_readl(offset); +} + +#define interrupt_pin(a) 0 +#define IRQ_POLARITY 1 + +#define NI_E_IRQ_FLAGS IRQF_SHARED + +#include "ni_mio_common.c" + +static int pcimio_ai_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size); +static int pcimio_ao_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size); +static int pcimio_gpct0_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size); +static int pcimio_gpct1_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size); +static int pcimio_dio_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size); + +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(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(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 = comedi_board(dev); + struct ni_private *devpriv = dev->private; + + /* Disable interrupts */ + devpriv->stc_writew(dev, 0, Interrupt_Control_Register); + + /* Initialise 6143 AI specific bits */ + ni_writeb(0x00, Magic_6143); /* Set G0,G1 DMA mode to E series version */ + ni_writeb(0x80, PipelineDelay_6143); /* Set EOCMode, ADCMode and pipelinedelay */ + ni_writeb(0x00, EOC_Set_6143); /* Set EOC Delay */ + + /* Set the FIFO half full level */ + ni_writel(board->ai_fifo_depth / 2, AIFIFO_Flag_6143); + + /* Strobe Relay disable bit */ + devpriv->ai_calib_source_enabled = 0; + ni_writew(devpriv->ai_calib_source | Calibration_Channel_6143_RelayOff, + Calibration_Channel_6143); + ni_writew(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]); + if (devpriv->mite) { + mite_unsetup(devpriv->mite); + mite_free(devpriv->mite); + } + } + 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->stc_writew = &m_series_stc_writew; + devpriv->stc_readw = &m_series_stc_readw; + devpriv->stc_writel = &m_series_stc_writel; + devpriv->stc_readl = &m_series_stc_readl; + } else { + devpriv->stc_writew = &e_series_win_out; + devpriv->stc_readw = &e_series_win_in; + devpriv->stc_writel = &win_out2; + devpriv->stc_readl = &win_in2; + } + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + pr_warn("error setting up mite\n"); + return ret; + } + + devpriv->ai_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->ai_mite_ring == NULL) + return -ENOMEM; + devpriv->ao_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->ao_mite_ring == NULL) + return -ENOMEM; + devpriv->cdo_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->cdo_mite_ring == NULL) + return -ENOMEM; + devpriv->gpct_mite_ring[0] = mite_alloc_ring(devpriv->mite); + if (devpriv->gpct_mite_ring[0] == NULL) + return -ENOMEM; + devpriv->gpct_mite_ring[1] = mite_alloc_ring(devpriv->mite); + if (devpriv->gpct_mite_ring[1] == NULL) + return -ENOMEM; + + if (board->reg_type & ni_reg_m_series_mask) + m_series_init_eeprom_buffer(dev); + if (board->reg_type == ni_reg_6143) + init_6143(dev); + + irq = mite_irq(devpriv->mite); + if (irq) { + ret = request_irq(irq, ni_E_interrupt, NI_E_IRQ_FLAGS, + dev->board_name, dev); + if (ret == 0) + dev->irq = irq; + } + + ret = ni_E_init(dev); + 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 int pcimio_ai_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size) +{ + 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, unsigned long new_size) +{ + 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, + unsigned long new_size) +{ + 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, + unsigned long new_size) +{ + 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, unsigned long new_size) +{ + 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 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 00000000000..f0630b7897b --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_stc.h @@ -0,0 +1,1507 @@ +/* + 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) { + printk("%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)) { + printk("%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)) { + printk("%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) { + printk("%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) { + printk("%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) { + printk("%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) { + printk("%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; +}; + +enum MSeries_Gi_DMA_Config_Bits { + Gi_DMA_BankSW_Error_Bit = 0x10, + Gi_DMA_Reset_Bit = 0x8, + Gi_DMA_Int_Enable_Bit = 0x4, + Gi_DMA_Write_Bit = 0x2, + Gi_DMA_Enable_Bit = 0x1, +}; + +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 { + int device_id; + int isapnp_id; + char *name; + + int n_adchan; + int adbits; + + int ai_fifo_depth; + unsigned int alwaysdither:1; + int gainlkup; + int ai_speed; + + int n_aochan; + int aobits; + int ao_fifo_depth; + const struct comedi_lrange *ao_range_table; + unsigned ao_speed; + + unsigned num_p0_dio_channels; + + int reg_type; + unsigned int ao_unipolar:1; + unsigned int has_8255:1; + unsigned int has_analog_trig:1; + + enum caldac_enum caldac[3]; +}; + +#define MAX_N_AO_CHAN 8 +#define NUM_GPCT 2 + +#define NI_PRIVATE_COMMON \ + uint16_t (*stc_readw)(struct comedi_device *dev, int register); \ + uint32_t (*stc_readl)(struct comedi_device *dev, int register); \ + void (*stc_writew)(struct comedi_device *dev, uint16_t value, int register); \ + void (*stc_writel)(struct comedi_device *dev, uint32_t value, int register); \ + \ + unsigned short dio_output; \ + unsigned short dio_control; \ + int ao0p, ao1p; \ + int lastchan; \ + int last_do; \ + int rt_irq; \ + int irqmask; \ + int aimode; \ + int ai_continuous; \ + int blocksize; \ + int n_left; \ + 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 ao[MAX_N_AO_CHAN]; \ + 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_cmd3; \ + 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 atrig_mode; \ + unsigned short atrig_high; \ + unsigned short atrig_low; \ + \ + unsigned short pwm_up_count; \ + unsigned short pwm_down_count; \ + \ + unsigned short ai_fifo_buffer[0x2000]; \ + uint8_t eeprom_buffer[M_SERIES_EEPROM_SIZE]; \ + uint32_t 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]; + +#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 00000000000..92691b491c2 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tio.c @@ -0,0 +1,1698 @@ +/* + 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. +*/ + +/* +Driver: ni_tio +Description: National Instruments general purpose counters +Devices: +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" + +static uint64_t ni_tio_clock_period_ps(const struct ni_gpct *counter, + unsigned generic_clock_source); +static unsigned ni_tio_generic_clock_src_select(const struct ni_gpct *counter); + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_Alternate_Sync_Bit(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_Alternate_Sync_Bit; + break; + case ni_gpct_variant_660x: + return Gi_660x_Alternate_Sync_Bit; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_Prescale_X2_Bit(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_Prescale_X2_Bit; + break; + case ni_gpct_variant_660x: + return Gi_660x_Prescale_X2_Bit; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_Prescale_X8_Bit(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_Prescale_X8_Bit; + break; + case ni_gpct_variant_660x: + return Gi_660x_Prescale_X8_Bit; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_HW_Arm_Select_Mask(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_HW_Arm_Select_Mask; + break; + case ni_gpct_variant_660x: + return Gi_660x_HW_Arm_Select_Mask; + break; + default: + BUG(); + break; + } + return 0; +} + +/* clock sources for ni_660x boards, get bits with Gi_Source_Select_Bits() */ +enum ni_660x_clock_source { + NI_660x_Timebase_1_Clock = 0x0, /* 20MHz */ + NI_660x_Source_Pin_i_Clock = 0x1, + NI_660x_Next_Gate_Clock = 0xa, + NI_660x_Timebase_2_Clock = 0x12, /* 100KHz */ + NI_660x_Next_TC_Clock = 0x13, + NI_660x_Timebase_3_Clock = 0x1e, /* 80MHz */ + NI_660x_Logic_Low_Clock = 0x1f, +}; +static const unsigned ni_660x_max_rtsi_channel = 6; +static inline unsigned NI_660x_RTSI_Clock(unsigned n) +{ + BUG_ON(n > ni_660x_max_rtsi_channel); + return 0xb + n; +} + +static const unsigned ni_660x_max_source_pin = 7; +static inline unsigned NI_660x_Source_Pin_Clock(unsigned n) +{ + BUG_ON(n > ni_660x_max_source_pin); + return 0x2 + n; +} + +/* clock sources for ni e and m series boards, get bits with Gi_Source_Select_Bits() */ +enum ni_m_series_clock_source { + NI_M_Series_Timebase_1_Clock = 0x0, /* 20MHz */ + NI_M_Series_Timebase_2_Clock = 0x12, /* 100KHz */ + NI_M_Series_Next_TC_Clock = 0x13, + NI_M_Series_Next_Gate_Clock = 0x14, /* when Gi_Src_SubSelect = 0 */ + NI_M_Series_PXI_Star_Trigger_Clock = 0x14, /* when Gi_Src_SubSelect = 1 */ + NI_M_Series_PXI10_Clock = 0x1d, + NI_M_Series_Timebase_3_Clock = 0x1e, /* 80MHz, when Gi_Src_SubSelect = 0 */ + NI_M_Series_Analog_Trigger_Out_Clock = 0x1e, /* when Gi_Src_SubSelect = 1 */ + NI_M_Series_Logic_Low_Clock = 0x1f, +}; +static const unsigned ni_m_series_max_pfi_channel = 15; +static inline unsigned NI_M_Series_PFI_Clock(unsigned n) +{ + BUG_ON(n > ni_m_series_max_pfi_channel); + if (n < 10) + return 1 + n; + else + return 0xb + n; +} + +static const unsigned ni_m_series_max_rtsi_channel = 7; +static inline unsigned NI_M_Series_RTSI_Clock(unsigned n) +{ + BUG_ON(n > ni_m_series_max_rtsi_channel); + if (n == 7) + return 0x1b; + else + return 0xb + n; +} + +enum ni_660x_gate_select { + NI_660x_Source_Pin_i_Gate_Select = 0x0, + NI_660x_Gate_Pin_i_Gate_Select = 0x1, + NI_660x_Next_SRC_Gate_Select = 0xa, + NI_660x_Next_Out_Gate_Select = 0x14, + NI_660x_Logic_Low_Gate_Select = 0x1f, +}; +static const unsigned ni_660x_max_gate_pin = 7; +static inline unsigned NI_660x_Gate_Pin_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_gate_pin); + return 0x2 + n; +} + +static inline unsigned NI_660x_RTSI_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_rtsi_channel); + return 0xb + n; +} + +enum ni_m_series_gate_select { + NI_M_Series_Timestamp_Mux_Gate_Select = 0x0, + NI_M_Series_AI_START2_Gate_Select = 0x12, + NI_M_Series_PXI_Star_Trigger_Gate_Select = 0x13, + NI_M_Series_Next_Out_Gate_Select = 0x14, + NI_M_Series_AI_START1_Gate_Select = 0x1c, + NI_M_Series_Next_SRC_Gate_Select = 0x1d, + NI_M_Series_Analog_Trigger_Out_Gate_Select = 0x1e, + NI_M_Series_Logic_Low_Gate_Select = 0x1f, +}; +static inline unsigned NI_M_Series_RTSI_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_m_series_max_rtsi_channel); + if (n == 7) + return 0x1b; + return 0xb + n; +} + +static inline unsigned NI_M_Series_PFI_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_m_series_max_pfi_channel); + if (n < 10) + return 1 + n; + return 0xb + n; +} + +static inline unsigned Gi_Source_Select_Bits(unsigned source) +{ + return (source << Gi_Source_Select_Shift) & Gi_Source_Select_Mask; +} + +static inline unsigned Gi_Gate_Select_Bits(unsigned gate_select) +{ + return (gate_select << Gi_Gate_Select_Shift) & Gi_Gate_Select_Mask; +} + +enum ni_660x_second_gate_select { + NI_660x_Source_Pin_i_Second_Gate_Select = 0x0, + NI_660x_Up_Down_Pin_i_Second_Gate_Select = 0x1, + NI_660x_Next_SRC_Second_Gate_Select = 0xa, + NI_660x_Next_Out_Second_Gate_Select = 0x14, + NI_660x_Selected_Gate_Second_Gate_Select = 0x1e, + NI_660x_Logic_Low_Second_Gate_Select = 0x1f, +}; +static const unsigned ni_660x_max_up_down_pin = 7; +static inline unsigned NI_660x_Up_Down_Pin_Second_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_up_down_pin); + return 0x2 + n; +} + +static inline unsigned NI_660x_RTSI_Second_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_rtsi_channel); + return 0xb + n; +} + +static const unsigned int counter_status_mask = + COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING; + +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) +{ + unsigned i; + + struct ni_gpct_device *counter_dev = + kzalloc(sizeof(struct ni_gpct_device), GFP_KERNEL); + if (counter_dev == NULL) + 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); + BUG_ON(num_counters == 0); + counter_dev->counters = + kzalloc(sizeof(struct ni_gpct) * num_counters, GFP_KERNEL); + if (counter_dev->counters == NULL) { + kfree(counter_dev); + return NULL; + } + for (i = 0; i < num_counters; ++i) { + counter_dev->counters[i].counter_dev = counter_dev; + spin_lock_init(&counter_dev->counters[i].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 == NULL) + return; + kfree(counter_dev->counters); + kfree(counter_dev); +} +EXPORT_SYMBOL_GPL(ni_gpct_device_destroy); + +static int ni_tio_second_gate_registers_present(const struct ni_gpct_device + *counter_dev) +{ + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + return 1; + break; + default: + BUG(); + break; + } + return 0; +} + +static void ni_tio_reset_count_and_disarm(struct ni_gpct *counter) +{ + unsigned cidx = counter->counter_index; + + write_register(counter, Gi_Reset_Bit(cidx), NITIO_RESET_REG(cidx)); +} + +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, counter_dev->regs[NITIO_AUTO_INC_REG(cidx)], + NITIO_AUTO_INC_REG(cidx)); + + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), + ~0, Gi_Synchronize_Gate_Bit); + + ni_tio_set_bits(counter, NITIO_MODE_REG(cidx), ~0, 0); + + counter_dev->regs[NITIO_LOADA_REG(cidx)] = 0x0; + write_register(counter, counter_dev->regs[NITIO_LOADA_REG(cidx)], + NITIO_LOADA_REG(cidx)); + + counter_dev->regs[NITIO_LOADB_REG(cidx)] = 0x0; + write_register(counter, counter_dev->regs[NITIO_LOADB_REG(cidx)], + 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_second_gate_registers_present(counter_dev)) { + counter_dev->regs[NITIO_GATE2_REG(cidx)] = 0x0; + write_register(counter, + counter_dev->regs[NITIO_GATE2_REG(cidx)], + 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); + +static unsigned int ni_tio_counter_status(struct ni_gpct *counter) +{ + unsigned cidx = counter->counter_index; + const unsigned bits = read_register(counter, + NITIO_SHARED_STATUS_REG(cidx)); + unsigned int status = 0; + + if (bits & Gi_Armed_Bit(cidx)) { + status |= COMEDI_COUNTER_ARMED; + if (bits & Gi_Counting_Bit(cidx)) + status |= COMEDI_COUNTER_COUNTING; + } + return status; +} + +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; + const uint64_t clock_period_ps = ni_tio_clock_period_ps(counter, + ni_tio_generic_clock_src_select + (counter)); + + if (ni_tio_counting_mode_registers_present(counter_dev) == 0) + return; + + switch (ni_tio_get_soft_copy(counter, counting_mode_reg) & Gi_Counting_Mode_Mask) { + case Gi_Counting_Mode_QuadratureX1_Bits: + case Gi_Counting_Mode_QuadratureX2_Bits: + case Gi_Counting_Mode_QuadratureX4_Bits: + case Gi_Counting_Mode_Sync_Source_Bits: + force_alt_sync = 1; + break; + default: + break; + } + /* 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_Alternate_Sync_Bit(counter_dev->variant), + Gi_Alternate_Sync_Bit(counter_dev->variant)); + } else { + ni_tio_set_bits(counter, counting_mode_reg, + Gi_Alternate_Sync_Bit(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_Source_Switching_Bit; + 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_Source_Switching_Bit; + break; + case NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS: + input_select_bits |= Gi_Gate_Select_Load_Source_Bit; + mode_reg_mask |= Gi_Gating_Mode_Mask; + mode_reg_values |= Gi_Level_Gating_Bits; + 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 counting_mode_bits = 0; + counting_mode_bits |= + (mode >> NI_GPCT_COUNTING_MODE_SHIFT) & + Gi_Counting_Mode_Mask; + counting_mode_bits |= + ((mode >> NI_GPCT_INDEX_PHASE_BITSHIFT) << + Gi_Index_Phase_Bitshift) & Gi_Index_Phase_Mask; + if (mode & NI_GPCT_INDEX_ENABLE_BIT) + counting_mode_bits |= Gi_Index_Mode_Bit; + ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), + Gi_Counting_Mode_Mask | Gi_Index_Phase_Mask | + Gi_Index_Mode_Bit, counting_mode_bits); + ni_tio_set_sync_mode(counter, 0); + } + + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), + Gi_Up_Down_Mask, + (mode >> NI_GPCT_COUNTING_DIRECTION_SHIFT) << + Gi_Up_Down_Shift); + + if (mode & NI_GPCT_OR_GATE_BIT) + input_select_bits |= Gi_Or_Gate_Bit; + if (mode & NI_GPCT_INVERT_OUTPUT_BIT) + input_select_bits |= Gi_Output_Polarity_Bit; + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), + Gi_Gate_Select_Load_Source_Bit | Gi_Or_Gate_Bit | + Gi_Output_Polarity_Bit, 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_Bit; + break; + case NI_GPCT_ARM_PAIRED_IMMEDIATE: + command_transient_bits |= Gi_Arm_Bit | Gi_Arm_Copy_Bit; + break; + default: + break; + } + if (ni_tio_counting_mode_registers_present(counter_dev)) { + unsigned counting_mode_bits = 0; + + 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 */ + unsigned hw_arm_select_bits = + (start_trigger << + Gi_HW_Arm_Select_Shift) & + Gi_HW_Arm_Select_Mask + (counter_dev->variant); + + counting_mode_bits |= + Gi_HW_Arm_Enable_Bit | + hw_arm_select_bits; + } else { + return -EINVAL; + } + break; + } + ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), + Gi_HW_Arm_Select_Mask + (counter_dev->variant) | + Gi_HW_Arm_Enable_Bit, + counting_mode_bits); + } + } else { + command_transient_bits |= Gi_Disarm_Bit; + } + 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_source_select_bits(unsigned int clock_source) +{ + unsigned ni_660x_clock; + unsigned i; + const unsigned clock_select_bits = + clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK; + + switch (clock_select_bits) { + case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Timebase_1_Clock; + break; + case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Timebase_2_Clock; + break; + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Timebase_3_Clock; + break; + case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Logic_Low_Clock; + break; + case NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Source_Pin_i_Clock; + break; + case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Next_Gate_Clock; + break; + case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Next_TC_Clock; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (clock_select_bits == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) { + ni_660x_clock = NI_660x_RTSI_Clock(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_source_pin; ++i) { + if (clock_select_bits == + NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i)) { + ni_660x_clock = NI_660x_Source_Pin_Clock(i); + break; + } + } + if (i <= ni_660x_max_source_pin) + break; + ni_660x_clock = 0; + BUG(); + break; + } + return Gi_Source_Select_Bits(ni_660x_clock); +} + +static unsigned ni_m_series_source_select_bits(unsigned int clock_source) +{ + unsigned ni_m_series_clock; + unsigned i; + const unsigned clock_select_bits = + clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK; + switch (clock_select_bits) { + case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Timebase_1_Clock; + break; + case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Timebase_2_Clock; + break; + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Timebase_3_Clock; + break; + case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Logic_Low_Clock; + break; + case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Next_Gate_Clock; + break; + case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Next_TC_Clock; + break; + case NI_GPCT_PXI10_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_PXI10_Clock; + break; + case NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_PXI_Star_Trigger_Clock; + break; + case NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Analog_Trigger_Out_Clock; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (clock_select_bits == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) { + ni_m_series_clock = NI_M_Series_RTSI_Clock(i); + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (clock_select_bits == NI_GPCT_PFI_CLOCK_SRC_BITS(i)) { + ni_m_series_clock = NI_M_Series_PFI_Clock(i); + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + break; + printk(KERN_ERR "invalid clock source 0x%lx\n", + (unsigned long)clock_source); + BUG(); + ni_m_series_clock = 0; + break; + } + return Gi_Source_Select_Bits(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_Source_Subselect_Bit; + 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_Source_Subselect_Bit; + break; + /* Gi_Source_Subselect doesn't matter */ + default: + return; + break; + } + 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 input_select_bits = 0; + static const uint64_t pico_per_nano = 1000; + +/*FIXME: validate clock source */ + switch (counter_dev->variant) { + case ni_gpct_variant_660x: + input_select_bits |= ni_660x_source_select_bits(clock_source); + break; + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + input_select_bits |= + ni_m_series_source_select_bits(clock_source); + break; + default: + BUG(); + break; + } + if (clock_source & NI_GPCT_INVERT_CLOCK_SRC_BIT) + input_select_bits |= Gi_Source_Polarity_Bit; + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), + Gi_Source_Select_Mask | Gi_Source_Polarity_Bit, + input_select_bits); + ni_tio_set_source_subselect(counter, clock_source); + if (ni_tio_counting_mode_registers_present(counter_dev)) { + const unsigned prescaling_mode = + clock_source & NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK; + unsigned counting_mode_bits = 0; + + switch (prescaling_mode) { + case NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS: + break; + case NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS: + counting_mode_bits |= + Gi_Prescale_X2_Bit(counter_dev->variant); + break; + case NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS: + counting_mode_bits |= + Gi_Prescale_X8_Bit(counter_dev->variant); + break; + default: + return -EINVAL; + break; + } + ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), + Gi_Prescale_X2_Bit(counter_dev->variant) | + Gi_Prescale_X8_Bit(counter_dev->variant), + counting_mode_bits); + } + counter->clock_period_ps = pico_per_nano * period_ns; + ni_tio_set_sync_mode(counter, 0); + return 0; +} + +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_Source_Polarity_Bit) + bits |= NI_GPCT_INVERT_CLOCK_SRC_BIT; + if (counting_mode_bits & Gi_Prescale_X2_Bit(counter_dev->variant)) + bits |= NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS; + if (counting_mode_bits & Gi_Prescale_X8_Bit(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 i; + const unsigned input_select = + (ni_tio_get_soft_copy(counter, NITIO_INPUT_SEL_REG(cidx)) & + Gi_Source_Select_Mask) >> Gi_Source_Select_Shift; + + switch (input_select) { + case NI_M_Series_Timebase_1_Clock: + clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS; + break; + case NI_M_Series_Timebase_2_Clock: + clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS; + break; + case NI_M_Series_Timebase_3_Clock: + if (counter_dev->regs[second_gate_reg] & + Gi_Source_Subselect_Bit) + clock_source = + NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS; + else + clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS; + break; + case NI_M_Series_Logic_Low_Clock: + clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS; + break; + case NI_M_Series_Next_Gate_Clock: + if (counter_dev->regs[second_gate_reg] & + Gi_Source_Subselect_Bit) + clock_source = NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS; + else + clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS; + break; + case NI_M_Series_PXI10_Clock: + clock_source = NI_GPCT_PXI10_CLOCK_SRC_BITS; + break; + case NI_M_Series_Next_TC_Clock: + clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (input_select == NI_M_Series_RTSI_Clock(i)) { + clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (input_select == NI_M_Series_PFI_Clock(i)) { + clock_source = NI_GPCT_PFI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + 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; + const unsigned input_select = + (ni_tio_get_soft_copy(counter, NITIO_INPUT_SEL_REG(cidx)) & + Gi_Source_Select_Mask) >> Gi_Source_Select_Shift; + unsigned i; + + switch (input_select) { + case NI_660x_Timebase_1_Clock: + clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS; + break; + case NI_660x_Timebase_2_Clock: + clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS; + break; + case NI_660x_Timebase_3_Clock: + clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS; + break; + case NI_660x_Logic_Low_Clock: + clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS; + break; + case NI_660x_Source_Pin_i_Clock: + clock_source = NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS; + break; + case NI_660x_Next_Gate_Clock: + clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS; + break; + case NI_660x_Next_TC_Clock: + clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (input_select == NI_660x_RTSI_Clock(i)) { + clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_source_pin; ++i) { + if (input_select == NI_660x_Source_Pin_Clock(i)) { + clock_source = + NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_660x_max_source_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: + return ni_m_series_clock_src_select(counter); + break; + case ni_gpct_variant_660x: + return ni_660x_clock_src_select(counter); + break; + default: + BUG(); + break; + } + return 0; +} + +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; + break; + } + + 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 void ni_tio_get_clock_src(struct ni_gpct *counter, + unsigned int *clock_source, + unsigned int *period_ns) +{ + static const unsigned pico_per_nano = 1000; + uint64_t temp64; + *clock_source = ni_tio_generic_clock_src_select(counter); + temp64 = ni_tio_clock_period_ps(counter, *clock_source); + do_div(temp64, pico_per_nano); + *period_ns = temp64; +} + +static void ni_tio_set_first_gate_modifiers(struct ni_gpct *counter, + unsigned int gate_source) +{ + const unsigned mode_mask = Gi_Gate_Polarity_Bit | Gi_Gating_Mode_Mask; + unsigned cidx = counter->counter_index; + unsigned mode_values = 0; + + if (gate_source & CR_INVERT) + mode_values |= Gi_Gate_Polarity_Bit; + if (gate_source & CR_EDGE) + mode_values |= Gi_Rising_Edge_Gating_Bits; + else + mode_values |= Gi_Level_Gating_Bits; + ni_tio_set_bits(counter, NITIO_MODE_REG(cidx), + mode_mask, mode_values); +} + +static int ni_660x_set_first_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + const unsigned selected_gate = CR_CHAN(gate_source); + unsigned cidx = counter->counter_index; + /* bits of selected_gate that may be meaningful to input select register */ + const unsigned selected_gate_mask = 0x1f; + unsigned ni_660x_gate_select; + unsigned i; + + switch (selected_gate) { + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + ni_660x_gate_select = NI_660x_Next_SRC_Gate_Select; + 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: + ni_660x_gate_select = selected_gate & selected_gate_mask; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (selected_gate == NI_GPCT_RTSI_GATE_SELECT(i)) { + ni_660x_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_gate_pin; ++i) { + if (selected_gate == NI_GPCT_GATE_PIN_GATE_SELECT(i)) { + ni_660x_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_660x_max_gate_pin) + break; + return -EINVAL; + break; + } + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), + Gi_Gate_Select_Mask, + Gi_Gate_Select_Bits(ni_660x_gate_select)); + return 0; +} + +static int ni_m_series_set_first_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + const unsigned selected_gate = CR_CHAN(gate_source); + unsigned cidx = counter->counter_index; + /* bits of selected_gate that may be meaningful to input select register */ + const unsigned selected_gate_mask = 0x1f; + unsigned ni_m_series_gate_select; + unsigned i; + + switch (selected_gate) { + 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: + ni_m_series_gate_select = selected_gate & selected_gate_mask; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (selected_gate == NI_GPCT_RTSI_GATE_SELECT(i)) { + ni_m_series_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (selected_gate == NI_GPCT_PFI_GATE_SELECT(i)) { + ni_m_series_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + break; + return -EINVAL; + break; + } + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), + Gi_Gate_Select_Mask, + Gi_Gate_Select_Bits(ni_m_series_gate_select)); + return 0; +} + +static int ni_660x_set_second_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned cidx = counter->counter_index; + const unsigned second_gate_reg = NITIO_GATE2_REG(cidx); + const unsigned selected_second_gate = CR_CHAN(gate_source); + /* bits of second_gate that may be meaningful to second gate register */ + static const unsigned selected_second_gate_mask = 0x1f; + unsigned ni_660x_second_gate_select; + unsigned i; + + switch (selected_second_gate) { + 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: + ni_660x_second_gate_select = + selected_second_gate & selected_second_gate_mask; + break; + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + ni_660x_second_gate_select = + NI_660x_Next_SRC_Second_Gate_Select; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (selected_second_gate == NI_GPCT_RTSI_GATE_SELECT(i)) { + ni_660x_second_gate_select = + selected_second_gate & + selected_second_gate_mask; + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_up_down_pin; ++i) { + if (selected_second_gate == + NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i)) { + ni_660x_second_gate_select = + selected_second_gate & + selected_second_gate_mask; + break; + } + } + if (i <= ni_660x_max_up_down_pin) + break; + return -EINVAL; + break; + } + counter_dev->regs[second_gate_reg] |= Gi_Second_Gate_Mode_Bit; + counter_dev->regs[second_gate_reg] &= ~Gi_Second_Gate_Select_Mask; + counter_dev->regs[second_gate_reg] |= + Gi_Second_Gate_Select_Bits(ni_660x_second_gate_select); + write_register(counter, counter_dev->regs[second_gate_reg], + second_gate_reg); + return 0; +} + +static int ni_m_series_set_second_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned cidx = counter->counter_index; + const unsigned second_gate_reg = NITIO_GATE2_REG(cidx); + const unsigned selected_second_gate = CR_CHAN(gate_source); + /* bits of second_gate that may be meaningful to second gate register */ + static const unsigned selected_second_gate_mask = 0x1f; + unsigned ni_m_series_second_gate_select; + + /* FIXME: We don't know what the m-series second gate codes are, so we'll just pass + the bits through for now. */ + switch (selected_second_gate) { + default: + ni_m_series_second_gate_select = + selected_second_gate & selected_second_gate_mask; + break; + } + counter_dev->regs[second_gate_reg] |= Gi_Second_Gate_Mode_Bit; + counter_dev->regs[second_gate_reg] &= ~Gi_Second_Gate_Select_Mask; + counter_dev->regs[second_gate_reg] |= + Gi_Second_Gate_Select_Bits(ni_m_series_second_gate_select); + write_register(counter, counter_dev->regs[second_gate_reg], + second_gate_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; + const unsigned second_gate_reg = NITIO_GATE2_REG(cidx); + + switch (gate_index) { + case 0: + if (CR_CHAN(gate_source) == NI_GPCT_DISABLED_GATE_SELECT) { + ni_tio_set_bits(counter, NITIO_MODE_REG(cidx), + Gi_Gating_Mode_Mask, + Gi_Gating_Disabled_Bits); + return 0; + } + ni_tio_set_first_gate_modifiers(counter, gate_source); + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + return ni_m_series_set_first_gate(counter, gate_source); + break; + case ni_gpct_variant_660x: + return ni_660x_set_first_gate(counter, gate_source); + break; + default: + BUG(); + break; + } + break; + case 1: + if (ni_tio_second_gate_registers_present(counter_dev) == 0) + return -EINVAL; + if (CR_CHAN(gate_source) == NI_GPCT_DISABLED_GATE_SELECT) { + counter_dev->regs[second_gate_reg] &= + ~Gi_Second_Gate_Mode_Bit; + write_register(counter, + counter_dev->regs[second_gate_reg], + second_gate_reg); + return 0; + } + if (gate_source & CR_INVERT) { + counter_dev->regs[second_gate_reg] |= + Gi_Second_Gate_Polarity_Bit; + } else { + counter_dev->regs[second_gate_reg] &= + ~Gi_Second_Gate_Polarity_Bit; + } + switch (counter_dev->variant) { + case ni_gpct_variant_m_series: + return ni_m_series_set_second_gate(counter, + gate_source); + break; + case ni_gpct_variant_660x: + return ni_660x_set_second_gate(counter, gate_source); + break; + default: + BUG(); + break; + } + break; + default: + return -EINVAL; + break; + } + 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; + + if (counter_dev->variant == ni_gpct_variant_m_series) { + unsigned int abz_reg, shift, mask; + + 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; + break; + } + mask = 0x1f << shift; + if (source > 0x1f) { + /* Disable gate */ + source = 0x1f; + } + 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; + } + return -EINVAL; +} + +static unsigned ni_660x_first_gate_to_generic_gate_source(unsigned + ni_660x_gate_select) +{ + unsigned i; + + switch (ni_660x_gate_select) { + case NI_660x_Source_Pin_i_Gate_Select: + return NI_GPCT_SOURCE_PIN_i_GATE_SELECT; + break; + case NI_660x_Gate_Pin_i_Gate_Select: + return NI_GPCT_GATE_PIN_i_GATE_SELECT; + break; + case NI_660x_Next_SRC_Gate_Select: + return NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_660x_Next_Out_Gate_Select: + return NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_660x_Logic_Low_Gate_Select: + return NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (ni_660x_gate_select == NI_660x_RTSI_Gate_Select(i)) { + return NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_gate_pin; ++i) { + if (ni_660x_gate_select == + NI_660x_Gate_Pin_Gate_Select(i)) { + return NI_GPCT_GATE_PIN_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_gate_pin) + break; + BUG(); + break; + } + return 0; +}; + +static unsigned ni_m_series_first_gate_to_generic_gate_source(unsigned + ni_m_series_gate_select) +{ + unsigned i; + + switch (ni_m_series_gate_select) { + case NI_M_Series_Timestamp_Mux_Gate_Select: + return NI_GPCT_TIMESTAMP_MUX_GATE_SELECT; + break; + case NI_M_Series_AI_START2_Gate_Select: + return NI_GPCT_AI_START2_GATE_SELECT; + break; + case NI_M_Series_PXI_Star_Trigger_Gate_Select: + return NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT; + break; + case NI_M_Series_Next_Out_Gate_Select: + return NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_M_Series_AI_START1_Gate_Select: + return NI_GPCT_AI_START1_GATE_SELECT; + break; + case NI_M_Series_Next_SRC_Gate_Select: + return NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_M_Series_Analog_Trigger_Out_Gate_Select: + return NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT; + break; + case NI_M_Series_Logic_Low_Gate_Select: + return NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (ni_m_series_gate_select == + NI_M_Series_RTSI_Gate_Select(i)) { + return NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (ni_m_series_gate_select == + NI_M_Series_PFI_Gate_Select(i)) { + return NI_GPCT_PFI_GATE_SELECT(i); + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + break; + BUG(); + break; + } + return 0; +}; + +static unsigned ni_660x_second_gate_to_generic_gate_source(unsigned + ni_660x_gate_select) +{ + unsigned i; + + switch (ni_660x_gate_select) { + case NI_660x_Source_Pin_i_Second_Gate_Select: + return NI_GPCT_SOURCE_PIN_i_GATE_SELECT; + break; + case NI_660x_Up_Down_Pin_i_Second_Gate_Select: + return NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT; + break; + case NI_660x_Next_SRC_Second_Gate_Select: + return NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_660x_Next_Out_Second_Gate_Select: + return NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_660x_Selected_Gate_Second_Gate_Select: + return NI_GPCT_SELECTED_GATE_GATE_SELECT; + break; + case NI_660x_Logic_Low_Second_Gate_Select: + return NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (ni_660x_gate_select == + NI_660x_RTSI_Second_Gate_Select(i)) { + return NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_up_down_pin; ++i) { + if (ni_660x_gate_select == + NI_660x_Up_Down_Pin_Second_Gate_Select(i)) { + return NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_up_down_pin) + break; + BUG(); + break; + } + return 0; +}; + +static unsigned ni_m_series_second_gate_to_generic_gate_source(unsigned + ni_m_series_gate_select) +{ + /*FIXME: the second gate sources for the m series are undocumented, so we just return + * the raw bits for now. */ + switch (ni_m_series_gate_select) { + default: + return ni_m_series_gate_select; + break; + } + 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; + const unsigned mode_bits = + ni_tio_get_soft_copy(counter, NITIO_MODE_REG(cidx)); + const unsigned second_gate_reg = NITIO_GATE2_REG(cidx); + unsigned gate_select_bits; + + switch (gate_index) { + case 0: + if ((mode_bits & Gi_Gating_Mode_Mask) == + Gi_Gating_Disabled_Bits) { + *gate_source = NI_GPCT_DISABLED_GATE_SELECT; + return 0; + } else { + gate_select_bits = + (ni_tio_get_soft_copy(counter, + NITIO_INPUT_SEL_REG(cidx)) & + Gi_Gate_Select_Mask) >> Gi_Gate_Select_Shift; + } + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + *gate_source = + ni_m_series_first_gate_to_generic_gate_source + (gate_select_bits); + break; + case ni_gpct_variant_660x: + *gate_source = + ni_660x_first_gate_to_generic_gate_source + (gate_select_bits); + break; + default: + BUG(); + break; + } + if (mode_bits & Gi_Gate_Polarity_Bit) + *gate_source |= CR_INVERT; + if ((mode_bits & Gi_Gating_Mode_Mask) != Gi_Level_Gating_Bits) + *gate_source |= CR_EDGE; + break; + case 1: + if ((mode_bits & Gi_Gating_Mode_Mask) == Gi_Gating_Disabled_Bits + || (counter_dev->regs[second_gate_reg] & + Gi_Second_Gate_Mode_Bit) + == 0) { + *gate_source = NI_GPCT_DISABLED_GATE_SELECT; + return 0; + } else { + gate_select_bits = + (counter_dev->regs[second_gate_reg] & + Gi_Second_Gate_Select_Mask) >> + Gi_Second_Gate_Select_Shift; + } + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + *gate_source = + ni_m_series_second_gate_to_generic_gate_source + (gate_select_bits); + break; + case ni_gpct_variant_660x: + *gate_source = + ni_660x_second_gate_to_generic_gate_source + (gate_select_bits); + break; + default: + BUG(); + break; + } + if (counter_dev->regs[second_gate_reg] & + Gi_Second_Gate_Polarity_Bit) { + *gate_source |= CR_INVERT; + } + /* second gate can't have edge/level mode set independently */ + if ((mode_bits & Gi_Gating_Mode_Mask) != Gi_Level_Gating_Bits) + *gate_source |= CR_EDGE; + break; + default: + return -EINVAL; + break; + } + 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; + + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + return ni_tio_set_counter_mode(counter, data[1]); + break; + case INSN_CONFIG_ARM: + return ni_tio_arm(counter, 1, data[1]); + break; + case INSN_CONFIG_DISARM: + ni_tio_arm(counter, 0, 0); + return 0; + break; + case INSN_CONFIG_GET_COUNTER_STATUS: + data[1] = ni_tio_counter_status(counter); + data[2] = counter_status_mask; + return 0; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + return ni_tio_set_clock_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ni_tio_get_clock_src(counter, &data[1], &data[2]); + return 0; + break; + case INSN_CONFIG_SET_GATE_SRC: + return ni_tio_set_gate_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_GET_GATE_SRC: + return ni_tio_get_gate_src(counter, data[1], &data[2]); + break; + case INSN_CONFIG_SET_OTHER_SRC: + return ni_tio_set_other_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_RESET: + ni_tio_reset_count_and_disarm(counter); + return 0; + break; + default: + break; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ni_tio_insn_config); + +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; + const unsigned channel = CR_CHAN(insn->chanspec); + unsigned cidx = counter->counter_index; + unsigned first_read; + unsigned second_read; + unsigned correct_read; + + if (insn->n < 1) + return 0; + switch (channel) { + case 0: + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), + Gi_Save_Trace_Bit, 0); + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), + Gi_Save_Trace_Bit, Gi_Save_Trace_Bit); + /* 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. */ + first_read = read_register(counter, NITIO_SW_SAVE_REG(cidx)); + second_read = read_register(counter, NITIO_SW_SAVE_REG(cidx)); + if (first_read != second_read) + correct_read = + read_register(counter, NITIO_SW_SAVE_REG(cidx)); + else + correct_read = first_read; + data[0] = correct_read; + return 0; + break; + case 1: + data[0] = counter_dev->regs[NITIO_LOADA_REG(cidx)]; + break; + case 2: + data[0] = counter_dev->regs[NITIO_LOADB_REG(cidx)]; + break; + } + return 0; +} +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)); + + if (bits & Gi_Next_Load_Source_Bit(cidx)) + return NITIO_LOADB_REG(cidx); + else + return 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_Bit); + /* restore state of load reg to whatever the user set last set it to */ + 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; + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_insn_write); + +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 00000000000..1056bf001e5 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tio.h @@ -0,0 +1,156 @@ +/* + 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_and_confirm(struct ni_gpct *, + int *gate_error, int *tc_error, + int *perm_stale_data, int *stale_data); + +#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 00000000000..15b81b8fc5c --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tio_internal.h @@ -0,0 +1,404 @@ +/* + 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 NITIO_CMD_REG(x) (NITIO_G0_CMD + (x)) +#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 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 NITIO_CNT_MODE_REG(x) (NITIO_G0_CNT_MODE + (x)) +#define NITIO_GATE2_REG(x) (NITIO_G0_GATE2 + (x)) +#define NITIO_SHARED_STATUS_REG(x) (NITIO_G01_STATUS + ((x) / 2)) +#define NITIO_RESET_REG(x) (NITIO_G01_RESET + ((x) / 2)) +#define NITIO_STATUS1_REG(x) (NITIO_G01_STATUS1 + ((x) / 2)) +#define NITIO_STATUS2_REG(x) (NITIO_G01_STATUS2 + ((x) / 2)) +#define NITIO_DMA_CFG_REG(x) (NITIO_G0_DMA_CFG + (x)) +#define NITIO_DMA_STATUS_REG(x) (NITIO_G0_DMA_STATUS + (x)) +#define NITIO_ABZ_REG(x) (NITIO_G0_ABZ + (x)) +#define NITIO_INT_ACK_REG(x) (NITIO_G0_INT_ACK + (x)) +#define NITIO_STATUS_REG(x) (NITIO_G0_STATUS + (x)) +#define NITIO_INT_ENA_REG(x) (NITIO_G0_INT_ENA + (x)) + +enum Gi_Auto_Increment_Reg_Bits { + Gi_Auto_Increment_Mask = 0xff +}; + +#define Gi_Up_Down_Shift 5 +enum Gi_Command_Reg_Bits { + Gi_Arm_Bit = 0x1, + Gi_Save_Trace_Bit = 0x2, + Gi_Load_Bit = 0x4, + Gi_Disarm_Bit = 0x10, + Gi_Up_Down_Mask = 0x3 << Gi_Up_Down_Shift, + Gi_Always_Down_Bits = 0x0 << Gi_Up_Down_Shift, + Gi_Always_Up_Bits = 0x1 << Gi_Up_Down_Shift, + Gi_Up_Down_Hardware_IO_Bits = 0x2 << Gi_Up_Down_Shift, + Gi_Up_Down_Hardware_Gate_Bits = 0x3 << Gi_Up_Down_Shift, + Gi_Write_Switch_Bit = 0x80, + Gi_Synchronize_Gate_Bit = 0x100, + Gi_Little_Big_Endian_Bit = 0x200, + Gi_Bank_Switch_Start_Bit = 0x400, + Gi_Bank_Switch_Mode_Bit = 0x800, + Gi_Bank_Switch_Enable_Bit = 0x1000, + Gi_Arm_Copy_Bit = 0x2000, + Gi_Save_Trace_Copy_Bit = 0x4000, + Gi_Disarm_Copy_Bit = 0x8000 +}; + +#define Gi_Index_Phase_Bitshift 5 +#define Gi_HW_Arm_Select_Shift 8 +enum Gi_Counting_Mode_Reg_Bits { + Gi_Counting_Mode_Mask = 0x7, + Gi_Counting_Mode_Normal_Bits = 0x0, + Gi_Counting_Mode_QuadratureX1_Bits = 0x1, + Gi_Counting_Mode_QuadratureX2_Bits = 0x2, + Gi_Counting_Mode_QuadratureX4_Bits = 0x3, + Gi_Counting_Mode_Two_Pulse_Bits = 0x4, + Gi_Counting_Mode_Sync_Source_Bits = 0x6, + Gi_Index_Mode_Bit = 0x10, + Gi_Index_Phase_Mask = 0x3 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_LowA_LowB = 0x0 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_LowA_HighB = 0x1 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_HighA_LowB = 0x2 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_HighA_HighB = 0x3 << Gi_Index_Phase_Bitshift, + /* from m-series example code, not documented in 660x register level + * manual */ + Gi_HW_Arm_Enable_Bit = 0x80, + /* from m-series example code, not documented in 660x register level + * manual */ + Gi_660x_HW_Arm_Select_Mask = 0x7 << Gi_HW_Arm_Select_Shift, + Gi_660x_Prescale_X8_Bit = 0x1000, + Gi_M_Series_Prescale_X8_Bit = 0x2000, + Gi_M_Series_HW_Arm_Select_Mask = 0x1f << Gi_HW_Arm_Select_Shift, + /* must be set for clocks over 40MHz, which includes synchronous + * counting and quadrature modes */ + Gi_660x_Alternate_Sync_Bit = 0x2000, + Gi_M_Series_Alternate_Sync_Bit = 0x4000, + /* from m-series example code, not documented in 660x register level + * manual */ + Gi_660x_Prescale_X2_Bit = 0x4000, + Gi_M_Series_Prescale_X2_Bit = 0x8000, +}; + +#define Gi_Source_Select_Shift 2 +#define Gi_Gate_Select_Shift 7 +enum Gi_Input_Select_Bits { + Gi_Read_Acknowledges_Irq = 0x1, /* not present on 660x */ + Gi_Write_Acknowledges_Irq = 0x2, /* not present on 660x */ + Gi_Source_Select_Mask = 0x7c, + Gi_Gate_Select_Mask = 0x1f << Gi_Gate_Select_Shift, + Gi_Gate_Select_Load_Source_Bit = 0x1000, + Gi_Or_Gate_Bit = 0x2000, + Gi_Output_Polarity_Bit = 0x4000, /* set to invert */ + Gi_Source_Polarity_Bit = 0x8000 /* set to invert */ +}; + +enum Gi_Mode_Bits { + Gi_Gating_Mode_Mask = 0x3, + Gi_Gating_Disabled_Bits = 0x0, + Gi_Level_Gating_Bits = 0x1, + Gi_Rising_Edge_Gating_Bits = 0x2, + Gi_Falling_Edge_Gating_Bits = 0x3, + Gi_Gate_On_Both_Edges_Bit = 0x4, /* used in conjunction with + * rising edge gating mode */ + Gi_Trigger_Mode_for_Edge_Gate_Mask = 0x18, + Gi_Edge_Gate_Starts_Stops_Bits = 0x0, + Gi_Edge_Gate_Stops_Starts_Bits = 0x8, + Gi_Edge_Gate_Starts_Bits = 0x10, + Gi_Edge_Gate_No_Starts_or_Stops_Bits = 0x18, + Gi_Stop_Mode_Mask = 0x60, + Gi_Stop_on_Gate_Bits = 0x00, + Gi_Stop_on_Gate_or_TC_Bits = 0x20, + Gi_Stop_on_Gate_or_Second_TC_Bits = 0x40, + Gi_Load_Source_Select_Bit = 0x80, + Gi_Output_Mode_Mask = 0x300, + Gi_Output_TC_Pulse_Bits = 0x100, + Gi_Output_TC_Toggle_Bits = 0x200, + Gi_Output_TC_or_Gate_Toggle_Bits = 0x300, + Gi_Counting_Once_Mask = 0xc00, + Gi_No_Hardware_Disarm_Bits = 0x000, + Gi_Disarm_at_TC_Bits = 0x400, + Gi_Disarm_at_Gate_Bits = 0x800, + Gi_Disarm_at_TC_or_Gate_Bits = 0xc00, + Gi_Loading_On_TC_Bit = 0x1000, + Gi_Gate_Polarity_Bit = 0x2000, + Gi_Loading_On_Gate_Bit = 0x4000, + Gi_Reload_Source_Switching_Bit = 0x8000 +}; + +#define Gi_Second_Gate_Select_Shift 7 +/*FIXME: m-series has a second gate subselect bit */ +/*FIXME: m-series second gate sources are undocumented (by NI)*/ +enum Gi_Second_Gate_Bits { + Gi_Second_Gate_Mode_Bit = 0x1, + Gi_Second_Gate_Select_Mask = 0x1f << Gi_Second_Gate_Select_Shift, + Gi_Second_Gate_Polarity_Bit = 0x2000, + Gi_Second_Gate_Subselect_Bit = 0x4000, /* m-series only */ + Gi_Source_Subselect_Bit = 0x8000 /* m-series only */ +}; +static inline unsigned Gi_Second_Gate_Select_Bits(unsigned second_gate_select) +{ + return (second_gate_select << Gi_Second_Gate_Select_Shift) & + Gi_Second_Gate_Select_Mask; +} + +enum Gxx_Status_Bits { + G0_Save_Bit = 0x1, + G1_Save_Bit = 0x2, + G0_Counting_Bit = 0x4, + G1_Counting_Bit = 0x8, + G0_Next_Load_Source_Bit = 0x10, + G1_Next_Load_Source_Bit = 0x20, + G0_Stale_Data_Bit = 0x40, + G1_Stale_Data_Bit = 0x80, + G0_Armed_Bit = 0x100, + G1_Armed_Bit = 0x200, + G0_No_Load_Between_Gates_Bit = 0x400, + G1_No_Load_Between_Gates_Bit = 0x800, + G0_TC_Error_Bit = 0x1000, + G1_TC_Error_Bit = 0x2000, + G0_Gate_Error_Bit = 0x4000, + G1_Gate_Error_Bit = 0x8000 +}; +static inline enum Gxx_Status_Bits Gi_Counting_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Counting_Bit; + return G0_Counting_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Armed_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Armed_Bit; + return G0_Armed_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Next_Load_Source_Bit(unsigned + counter_index) +{ + if (counter_index % 2) + return G1_Next_Load_Source_Bit; + return G0_Next_Load_Source_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Stale_Data_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Stale_Data_Bit; + return G0_Stale_Data_Bit; +} + +static inline enum Gxx_Status_Bits Gi_TC_Error_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_TC_Error_Bit; + return G0_TC_Error_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Gate_Error_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Gate_Error_Bit; + return G0_Gate_Error_Bit; +} + +/* joint reset register bits */ +static inline unsigned Gi_Reset_Bit(unsigned counter_index) +{ + return 0x1 << (2 + (counter_index % 2)); +} + +enum Gxx_Joint_Status2_Bits { + G0_Output_Bit = 0x1, + G1_Output_Bit = 0x2, + G0_HW_Save_Bit = 0x1000, + G1_HW_Save_Bit = 0x2000, + G0_Permanent_Stale_Bit = 0x4000, + G1_Permanent_Stale_Bit = 0x8000 +}; +static inline enum Gxx_Joint_Status2_Bits Gi_Permanent_Stale_Bit(unsigned + counter_index) +{ + if (counter_index % 2) + return G1_Permanent_Stale_Bit; + return G0_Permanent_Stale_Bit; +} + +enum Gi_DMA_Config_Reg_Bits { + Gi_DMA_Enable_Bit = 0x1, + Gi_DMA_Write_Bit = 0x2, + Gi_DMA_Int_Bit = 0x4 +}; + +enum Gi_DMA_Status_Reg_Bits { + Gi_DMA_Readbank_Bit = 0x2000, + Gi_DRQ_Error_Bit = 0x4000, + Gi_DRQ_Status_Bit = 0x8000 +}; + +enum G02_Interrupt_Acknowledge_Bits { + G0_Gate_Error_Confirm_Bit = 0x20, + G0_TC_Error_Confirm_Bit = 0x40 +}; +enum G13_Interrupt_Acknowledge_Bits { + G1_Gate_Error_Confirm_Bit = 0x2, + G1_TC_Error_Confirm_Bit = 0x4 +}; +static inline unsigned Gi_Gate_Error_Confirm_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Gate_Error_Confirm_Bit; + return G0_Gate_Error_Confirm_Bit; +} + +static inline unsigned Gi_TC_Error_Confirm_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_TC_Error_Confirm_Bit; + return G0_TC_Error_Confirm_Bit; +} + +/* bits that are the same in G0/G2 and G1/G3 interrupt acknowledge registers */ +enum Gxx_Interrupt_Acknowledge_Bits { + Gi_TC_Interrupt_Ack_Bit = 0x4000, + Gi_Gate_Interrupt_Ack_Bit = 0x8000 +}; + +enum Gi_Status_Bits { + Gi_Gate_Interrupt_Bit = 0x4, + Gi_TC_Bit = 0x8, + Gi_Interrupt_Bit = 0x8000 +}; + +enum G02_Interrupt_Enable_Bits { + G0_TC_Interrupt_Enable_Bit = 0x40, + G0_Gate_Interrupt_Enable_Bit = 0x100 +}; +enum G13_Interrupt_Enable_Bits { + G1_TC_Interrupt_Enable_Bit = 0x200, + G1_Gate_Interrupt_Enable_Bit = 0x400 +}; +static inline unsigned Gi_Gate_Interrupt_Enable_Bit(unsigned counter_index) +{ + unsigned bit; + + if (counter_index % 2) + bit = G1_Gate_Interrupt_Enable_Bit; + else + bit = G0_Gate_Interrupt_Enable_Bit; + return bit; +} + +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; + break; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + return 1; + break; + 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 00000000000..2557ab48cb6 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tiocmd.c @@ -0,0 +1,496 @@ +/* + 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. +*/ + +/* +Driver: ni_tiocmd +Description: National Instruments general purpose counters command support +Devices: +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 "comedi_fc.h" +#include "ni_tio_internal.h" +#include "mite.h" + +static void ni_tio_configure_dma(struct ni_gpct *counter, short enable, + short read_not_write) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned cidx = counter->counter_index; + unsigned input_select_bits = 0; + + if (enable) { + if (read_not_write) + input_select_bits |= Gi_Read_Acknowledges_Irq; + else + input_select_bits |= Gi_Write_Acknowledges_Irq; + } + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), + Gi_Read_Acknowledges_Irq | Gi_Write_Acknowledges_Irq, + input_select_bits); + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + break; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + { + unsigned gi_dma_config_bits = 0; + + if (enable) { + gi_dma_config_bits |= Gi_DMA_Enable_Bit; + gi_dma_config_bits |= Gi_DMA_Int_Bit; + } + if (read_not_write == 0) + gi_dma_config_bits |= Gi_DMA_Write_Bit; + ni_tio_set_bits(counter, NITIO_DMA_CFG_REG(cidx), + Gi_DMA_Enable_Bit | Gi_DMA_Int_Bit | + Gi_DMA_Write_Bit, gi_dma_config_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 retval = 0; + + BUG_ON(counter == NULL); + + 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 + retval = -EIO; + spin_unlock_irqrestore(&counter->lock, flags); + if (retval < 0) + return retval; + retval = ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE); + s->async->inttrig = NULL; + + return retval; +} + +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 retval = 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_Bit, 0); + ni_tio_configure_dma(counter, 1, 1); + switch (cmd->start_src) { + case TRIG_NOW: + async->inttrig = NULL; + mite_dma_arm(counter->mite_chan); + retval = ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE); + break; + case TRIG_INT: + async->inttrig = &ni_tio_input_inttrig; + break; + case TRIG_EXT: + async->inttrig = NULL; + mite_dma_arm(counter->mite_chan); + retval = ni_tio_arm(counter, 1, cmd->start_arg); + break; + case TRIG_OTHER: + async->inttrig = NULL; + mite_dma_arm(counter->mite_chan); + break; + default: + BUG(); + break; + } + return retval; +} + +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, 1, 0); + 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 & TRIG_WAKE_EOS) { + ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx), + Gi_Gate_Interrupt_Enable_Bit(cidx), + Gi_Gate_Interrupt_Enable_Bit(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 == NULL) { + 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 |= cfc_check_trigger_src(&cmd->start_src, sources); + + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_EXT | TRIG_OTHER); + err |= cfc_check_trigger_src(&cmd->convert_src, + TRIG_NOW | TRIG_EXT | TRIG_OTHER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src != TRIG_EXT) + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + 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, 0, 0); + + ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx), + Gi_Gate_Interrupt_Enable_Bit(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; + break; + case ni_gpct_variant_e_series: + spin_lock_irqsave(&counter->lock, flags); + { + if (counter->mite_chan == NULL || + counter->mite_chan->dir != COMEDI_INPUT || + (mite_done(counter->mite_chan))) { + retval = 1; + } + } + spin_unlock_irqrestore(&counter->lock, flags); + break; + } + return retval; +} + +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_Bit(cidx)) { + ack |= Gi_Gate_Error_Confirm_Bit(cidx); + if (gate_error) { + /*660x don't support automatic acknowledgement + 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_Bit(cidx)) { + ack |= Gi_TC_Error_Confirm_Bit(cidx); + if (tc_error) + *tc_error = 1; + } + if (gi_status & Gi_TC_Bit) + ack |= Gi_TC_Interrupt_Ack_Bit; + if (gi_status & Gi_Gate_Interrupt_Bit) { + if (should_ack_gate(counter)) + ack |= Gi_Gate_Interrupt_Ack_Bit; + } + 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_Bit) { + if (gxx_status & Gi_Stale_Data_Bit(cidx)) { + if (stale_data) + *stale_data = 1; + } + if (read_register(counter, NITIO_STATUS2_REG(cidx)) & + Gi_Permanent_Stale_Bit(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; + } + } +} +EXPORT_SYMBOL_GPL(ni_tio_acknowledge_and_confirm); + +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_Bit) { + 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 == NULL) { + 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/pcl711.c b/drivers/staging/comedi/drivers/pcl711.c new file mode 100644 index 00000000000..c38d97a9a89 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl711.c @@ -0,0 +1,570 @@ +/* + * 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] + * (Advantech) PCL-711B [pcl711b] + * (AdLink) ACL-8112HG [acl8112hg] + * (AdLink) 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_fc.h" +#include "8253.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, + }, +}; + +struct pcl711_private { + unsigned int ntrig; + unsigned int ao_readback[2]; + unsigned int divisor1; + unsigned int divisor2; +}; + +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 pcl711_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int data; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + data = pcl711_ai_get_sample(dev, s); + + outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); + + if (comedi_buf_put(s, data) == 0) { + s->async->events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } else { + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + if (cmd->stop_src == TRIG_COUNT && !(--devpriv->ntrig)) { + pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); + s->async->events |= COMEDI_CB_EOA; + } + } + comedi_event(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) +{ + struct pcl711_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_EXT) { + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } else { +#define MAX_SPEED 1000 + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + MAX_SPEED); + } + + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_NONE) + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4 */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + return 0; +} + +static void pcl711_ai_load_counters(struct comedi_device *dev) +{ + struct pcl711_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + PCL711_TIMER_BASE; + + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + + i8254_write(timer_base, 0, 1, devpriv->divisor1); + i8254_write(timer_base, 0, 2, devpriv->divisor2); +} + +static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcl711_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + pcl711_set_changain(dev, s, cmd->chanlist[0]); + + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg == 0) { + /* an empty acquisition */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return 0; + } + devpriv->ntrig = cmd->stop_arg; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + pcl711_ai_load_counters(dev); + 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) +{ + struct pcl711_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = devpriv->ao_readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + pcl711_ao_write(dev, chan, val); + } + devpriv->ao_readback[chan] = val; + + return insn->n; +} + +static int pcl711_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcl711_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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 = comedi_board(dev); + struct pcl711_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; + + 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]; + } + + 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; + s->insn_read = pcl711_ao_insn_read; + + /* 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 00000000000..8af13e790ad --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl724.c @@ -0,0 +1,155 @@ +/* + * 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] + * (Advantech) PCL-722 [pcl722] + * (Advantech) PCL-731 [pcl731] + * (ADLink) ACL-7122 [acl7122] + * (ADLink) ACL-7124 [acl7124] + * (ADLink) PET-48DIO [pet48dio] + * (WinSystems) PCM-IO48 [pcmio48] + * 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" + +#define SIZE_8255 4 + +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 */ + }, +}; + +static int pcl724_8255mapped_io(int dir, int port, int data, + unsigned long iobase) +{ + int movport = SIZE_8255 * (iobase >> 12); + + iobase &= 0x0fff; + + if (dir) { + outb(port + movport, iobase); + outb(data, iobase + 1); + return 0; + } else { + outb(port + movport, iobase); + return inb(iobase + 1); + } +} + +static int pcl724_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct pcl724_board *board = comedi_board(dev); + 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 { + iobase = dev->iobase + (i * SIZE_8255); + ret = subdev_8255_init(dev, s, NULL, iobase); + } + 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 00000000000..74f6489bd12 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl726.c @@ -0,0 +1,455 @@ +/* + * 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] + * (Advantech) PCL-727 [pcl727] + * (Advantech) PCL-728 [pcl728] + * (ADLink) ACL-6126 [acl6126] + * (ADLink) 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" + +#include "comedi_fc.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 ao_readback[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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + 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_put(s, 0); + s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); + comedi_event(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) +{ + struct pcl726_private *devpriv = dev->private; + 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]; + devpriv->ao_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_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcl726_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[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 = comedi_board(dev); + 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 = comedi_board(dev); + 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 = comedi_board(dev); + 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; + s->insn_read = pcl726_ao_insn_read; + + 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 00000000000..7fb044ce399 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl730.c @@ -0,0 +1,337 @@ +/* + * 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] + * (ICP) ISO-730 [iso730] + * (Adlink) ACL-7130 [acl7130] + * (Advantech) PCM-3730 [pcm3730] + * (Advantech) PCL-725 [pcl725] + * (ICP) P8R8-DIO [p16r16dio] + * (Adlink) ACL-7225b [acl7225b] + * (ICP) P16R16-DIO [p16r16dio] + * (Advantech) PCL-733 [pcl733] + * (Advantech) PCL-734 [pcl734] + * (Diamond Systems) OPMM-1616-XT [opmm-1616-xt] + * (Diamond Systems) PEARL-MM-P [prearl-mm-p] + * 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) + */ + +struct pcl730_board { + const char *name; + unsigned int io_range; + unsigned is_pcl725:1; + unsigned is_acl7225b: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, + }, +}; + +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 = comedi_board(dev); + 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_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 00000000000..4c1b9470647 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl812.c @@ -0,0 +1,1437 @@ +/* + * 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 "../comedidev.h" + +#include <linux/delay.h> +#include <linux/io.h> +#include <asm/dma.h> + +#include "comedi_fc.h" +#include "8253.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 { + unsigned char dma; /* >0 use dma ( usedDMA channel) */ + 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 ai_act_scan; /* how many scans we finished */ + unsigned int dmapages; + unsigned int hwdmasize; + unsigned long dmabuf[2]; /* PTR to DMA buf */ + unsigned int hwdmaptr[2]; /* HW PTR to DMA buf */ + unsigned int dmabytestomove[2]; /* how many bytes DMA transfer */ + int next_dma_buf; /* which buffer is next to use */ + unsigned int dma_runs_to_end; /* how many times we must switch DMA buffers */ + unsigned int last_dma_run; /* how many bytes to transfer on last DMA buffer */ + unsigned int max_812_ai_mode0_rangewait; /* setling time for gain */ + unsigned int ao_readback[2]; /* data for AO readback */ + unsigned int divisor1; + unsigned int divisor2; + 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_start_pacer(struct comedi_device *dev, bool load_timers) +{ + struct pcl812_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + PCL812_TIMER_BASE; + + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + udelay(1); + + if (load_timers) { + i8254_write(timer_base, 0, 2, devpriv->divisor2); + i8254_write(timer_base, 0, 1, devpriv->divisor1); + } +} + +static void pcl812_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl812_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int dma_flags; + unsigned int bytes; + + /* we use EOS, so adapt DMA buffer to one scan */ + if (devpriv->ai_eos) { + devpriv->dmabytestomove[0] = cfc_bytes_per_scan(s); + devpriv->dmabytestomove[1] = cfc_bytes_per_scan(s); + devpriv->dma_runs_to_end = 1; + } else { + devpriv->dmabytestomove[0] = devpriv->hwdmasize; + devpriv->dmabytestomove[1] = devpriv->hwdmasize; + if (s->async->prealloc_bufsz < devpriv->hwdmasize) { + devpriv->dmabytestomove[0] = + s->async->prealloc_bufsz; + devpriv->dmabytestomove[1] = + s->async->prealloc_bufsz; + } + if (cmd->stop_src == TRIG_NONE) { + devpriv->dma_runs_to_end = 1; + } else { + /* how many samples we must transfer? */ + bytes = cmd->stop_arg * cfc_bytes_per_scan(s); + + /* how many DMA pages we must fill */ + devpriv->dma_runs_to_end = + bytes / devpriv->dmabytestomove[0]; + + /* on last dma transfer must be moved */ + devpriv->last_dma_run = + bytes % devpriv->dmabytestomove[0]; + if (devpriv->dma_runs_to_end == 0) + devpriv->dmabytestomove[0] = + devpriv->last_dma_run; + devpriv->dma_runs_to_end--; + } + } + if (devpriv->dmabytestomove[0] > devpriv->hwdmasize) { + devpriv->dmabytestomove[0] = devpriv->hwdmasize; + devpriv->ai_eos = 0; + } + if (devpriv->dmabytestomove[1] > devpriv->hwdmasize) { + devpriv->dmabytestomove[1] = devpriv->hwdmasize; + devpriv->ai_eos = 0; + } + devpriv->next_dma_buf = 0; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count(devpriv->dma, devpriv->dmabytestomove[0]); + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); +} + +static void pcl812_ai_setup_next_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl812_private *devpriv = dev->private; + unsigned long dma_flags; + + devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; + disable_dma(devpriv->dma); + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[devpriv->next_dma_buf]); + if (devpriv->ai_eos) { + set_dma_count(devpriv->dma, + devpriv->dmabytestomove[devpriv->next_dma_buf]); + } else { + if (devpriv->dma_runs_to_end) { + set_dma_count(devpriv->dma, + devpriv->dmabytestomove[devpriv-> + next_dma_buf]); + } else { + set_dma_count(devpriv->dma, devpriv->last_dma_run); + } + devpriv->dma_runs_to_end--; + } + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); +} + +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 = comedi_board(dev); + struct pcl812_private *devpriv = dev->private; + int err = 0; + unsigned int flags; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + + if (devpriv->use_ext_trg) + flags = TRIG_EXT; + else + flags = TRIG_TIMER; + err |= cfc_check_trigger_src(&cmd->convert_src, flags); + + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + board->ai_ns_min); + else /* TRIG_EXT */ + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_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_cmd *cmd = &s->async->cmd; + unsigned int ctrl = 0; + unsigned int i; + + pcl812_start_pacer(dev, false); + + pcl812_ai_set_chan_range(dev, cmd->chanlist[0], 1); + + if (devpriv->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_act_scan = 0; + devpriv->ai_poll_ptr = 0; + s->async->cur_chan = 0; + + /* don't we want wake up every scan? */ + if (cmd->flags & TRIG_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) + pcl812_ai_setup_dma(dev, s); + + switch (cmd->convert_src) { + case TRIG_TIMER: + pcl812_start_pacer(dev, 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 pcl812_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + s->async->events |= COMEDI_CB_BLOCK; + + s->async->cur_chan++; + if (s->async->cur_chan >= cmd->chanlist_len) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + s->async->events |= COMEDI_CB_EOS; + } + + if (cmd->stop_src == TRIG_COUNT && + devpriv->ai_act_scan >= cmd->stop_arg) { + /* all data sampled */ + 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 next_chan; + + 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_EOA | COMEDI_CB_ERROR; + return; + } + + comedi_buf_put(s, pcl812_ai_get_sample(dev, s)); + + /* Set up next channel. Added by abbotti 2010-01-20, but untested. */ + next_chan = s->async->cur_chan + 1; + if (next_chan >= cmd->chanlist_len) + next_chan = 0; + if (cmd->chanlist[s->async->cur_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; + + for (i = len; i; i--) { + comedi_buf_put(s, ptr[bufptr++]); + + 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; + int len, bufptr; + unsigned short *ptr; + + ptr = (unsigned short *)devpriv->dmabuf[devpriv->next_dma_buf]; + len = (devpriv->dmabytestomove[devpriv->next_dma_buf] >> 1) - + devpriv->ai_poll_ptr; + + pcl812_ai_setup_next_dma(dev, s); + + bufptr = devpriv->ai_poll_ptr; + devpriv->ai_poll_ptr = 0; + + transfer_from_dma_buf(dev, s, ptr, bufptr, len); +} + +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); + + cfc_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; + unsigned long flags; + unsigned int top1, top2, i; + + if (!devpriv->ai_dma) + return 0; /* poll is valid only for DMA transfer */ + + spin_lock_irqsave(&dev->spinlock, flags); + + for (i = 0; i < 10; i++) { + /* where is now DMA */ + top1 = get_dma_residue(devpriv->ai_dma); + top2 = get_dma_residue(devpriv->ai_dma); + if (top1 == top2) + break; + } + + if (top1 != top2) { + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + /* where is now DMA in buffer */ + top1 = devpriv->dmabytestomove[1 - devpriv->next_dma_buf] - top1; + top1 >>= 1; /* sample position */ + top2 = top1 - devpriv->ai_poll_ptr; + if (top2 < 1) { /* no new samples */ + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + transfer_from_dma_buf(dev, s, + (void *)devpriv->dmabuf[1 - + devpriv->next_dma_buf], + devpriv->ai_poll_ptr, top2); + + devpriv->ai_poll_ptr = top1; /* new buffer position */ + + spin_unlock_irqrestore(&dev->spinlock, flags); + + return s->async->buf_write_count - s->async->buf_read_count; +} + +static int pcl812_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl812_private *devpriv = dev->private; + + if (devpriv->ai_dma) + disable_dma(devpriv->dma); + + outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG, + dev->iobase + PCL812_CTRL_REG); + pcl812_start_pacer(dev, 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) +{ + struct pcl812_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + outb((data[i] & 0xff), + dev->iobase + PCL812_AO_LSB_REG(chan)); + outb((data[i] >> 8) & 0x0f, + dev->iobase + PCL812_AO_MSB_REG(chan)); + devpriv->ao_readback[chan] = data[i]; + } + + return insn->n; +} + +static int pcl812_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcl812_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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 = comedi_board(dev); + 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); + + /* stop pacer */ + if (board->IRQbits) + pcl812_start_pacer(dev, false); + + /* + * 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 = comedi_board(dev); + 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 int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl812_board *board = comedi_board(dev); + struct pcl812_private *devpriv; + struct comedi_subdevice *s; + int n_subdevices; + int subdev; + int ret; + int i; + + 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 ((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 && + (it->options[2] == 3 || it->options[2] == 1)) { + ret = request_dma(it->options[2], dev->board_name); + if (ret) { + dev_err(dev->class_dev, + "unable to request DMA channel %d\n", + it->options[2]); + return -EBUSY; + } + devpriv->dma = it->options[2]; + + devpriv->dmapages = 1; /* we want 8KB */ + devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE; + + for (i = 0; i < 2; i++) { + unsigned long dmabuf; + + dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages); + if (!dmabuf) + return -ENOMEM; + + devpriv->dmabuf[i] = dmabuf; + devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf); + } + } + + /* 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; + s->insn_read = pcl812_ao_insn_read; + s->insn_write = pcl812_ao_insn_write; + 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; + } + 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) +{ + struct pcl812_private *devpriv = dev->private; + + if (devpriv) { + if (devpriv->dmabuf[0]) + free_pages(devpriv->dmabuf[0], devpriv->dmapages); + if (devpriv->dmabuf[1]) + free_pages(devpriv->dmabuf[1], devpriv->dmapages); + if (devpriv->dma) + free_dma(devpriv->dma); + } + 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 00000000000..d9ca7fe16c9 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl816.c @@ -0,0 +1,811 @@ +/* + 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 "../comedidev.h" + +#include <linux/gfp.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <asm/dma.h> + +#include "comedi_fc.h" +#include "8253.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 { + unsigned int dma; /* used DMA, 0=don't use DMA */ + unsigned int dmapages; + unsigned int hwdmasize; + unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */ + unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */ + int next_dma_buf; /* which DMA buffer will be used next round */ + long dma_runs_to_end; /* how many we must permorm DMA transfer to end of record */ + unsigned long last_dma_run; /* how many bytes we must transfer on last DMA page */ + int ai_act_scan; /* how many scans we finished */ + unsigned int ai_poll_ptr; /* how many sampes transfer poll */ + unsigned int divisor1; + unsigned int divisor2; + unsigned int ai_cmd_running:1; + unsigned int ai_cmd_canceled:1; +}; + +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int chanlen); + +static void pcl816_start_pacer(struct comedi_device *dev, bool load_counters) +{ + struct pcl816_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE; + + i8254_set_mode(timer_base, 0, 0, I8254_MODE1 | I8254_BINARY); + i8254_write(timer_base, 0, 0, 0x00ff); + udelay(1); + + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + udelay(1); + + if (load_counters) { + i8254_write(timer_base, 0, 2, devpriv->divisor2); + i8254_write(timer_base, 0, 1, devpriv->divisor1); + } +} + +static void pcl816_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl816_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int dma_flags; + unsigned int bytes; + + bytes = devpriv->hwdmasize; + if (cmd->stop_src == TRIG_COUNT) { + /* how many */ + bytes = cmd->stop_arg * cfc_bytes_per_scan(s); + + /* how many DMA pages we must fill */ + devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize; + + /* on last dma transfer must be moved */ + devpriv->last_dma_run = bytes % devpriv->hwdmasize; + devpriv->dma_runs_to_end--; + if (devpriv->dma_runs_to_end >= 0) + bytes = devpriv->hwdmasize; + } else + devpriv->dma_runs_to_end = -1; + + devpriv->next_dma_buf = 0; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count(devpriv->dma, bytes); + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); +} + +static void pcl816_ai_setup_next_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl816_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long dma_flags; + + disable_dma(devpriv->dma); + if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) { + /* switch dma bufs */ + devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); + set_dma_addr(devpriv->dma, + devpriv->hwdmaptr[devpriv->next_dma_buf]); + if (devpriv->dma_runs_to_end) + set_dma_count(devpriv->dma, devpriv->hwdmasize); + else + set_dma_count(devpriv->dma, devpriv->last_dma_run); + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); + } + + devpriv->dma_runs_to_end--; +} + +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 pcl816_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + s->async->events |= COMEDI_CB_BLOCK; + + s->async->cur_chan++; + if (s->async->cur_chan >= cmd->chanlist_len) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + s->async->events |= COMEDI_CB_EOS; + } + + if (cmd->stop_src == TRIG_COUNT && + devpriv->ai_act_scan >= cmd->stop_arg) { + /* all data sampled */ + 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) +{ + int i; + + for (i = 0; i < len; i++) { + comedi_buf_put(s, ptr[bufptr++]); + + 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; + unsigned short *ptr; + unsigned int bufptr; + unsigned int len; + + 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; + } + + ptr = (unsigned short *)devpriv->dmabuf[devpriv->next_dma_buf]; + + pcl816_ai_setup_next_dma(dev, s); + + len = (devpriv->hwdmasize >> 1) - devpriv->ai_poll_ptr; + bufptr = devpriv->ai_poll_ptr; + devpriv->ai_poll_ptr = 0; + + transfer_from_dma_buf(dev, s, ptr, bufptr, len); + + pcl816_ai_clear_eoc(dev); + + cfc_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int pcl816_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + struct pcl816_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_EXT | TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000); + else /* TRIG_EXT */ + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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; + i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_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_cmd *cmd = &s->async->cmd; + unsigned int ctrl; + unsigned int seglen; + + if (devpriv->ai_cmd_running) + return -EBUSY; + + pcl816_start_pacer(dev, false); + + 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_act_scan = 0; + s->async->cur_chan = 0; + devpriv->ai_cmd_running = 1; + devpriv->ai_poll_ptr = 0; + devpriv->ai_cmd_canceled = 0; + + pcl816_ai_setup_dma(dev, s); + + pcl816_start_pacer(dev, 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((devpriv->dma << 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; + unsigned long flags; + unsigned int top1, top2, i; + + spin_lock_irqsave(&dev->spinlock, flags); + + for (i = 0; i < 20; i++) { + top1 = get_dma_residue(devpriv->dma); /* where is now DMA */ + top2 = get_dma_residue(devpriv->dma); + if (top1 == top2) + break; + } + if (top1 != top2) { + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + /* where is now DMA in buffer */ + top1 = devpriv->hwdmasize - top1; + top1 >>= 1; /* sample position */ + top2 = top1 - devpriv->ai_poll_ptr; + if (top2 < 1) { /* no new samples */ + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + transfer_from_dma_buf(dev, s, + (unsigned short *)devpriv->dmabuf[devpriv-> + next_dma_buf], + devpriv->ai_poll_ptr, top2); + + devpriv->ai_poll_ptr = top1; /* new buffer position */ + spin_unlock_irqrestore(&dev->spinlock, flags); + + cfc_handle_events(dev, s); + + return s->async->buf_write_count - s->async->buf_read_count; +} + +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); + + /* Stop pacer */ + i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0, + 2, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0, + 1, I8254_MODE0 | I8254_BINARY); + + devpriv->ai_cmd_running = 0; + devpriv->ai_cmd_canceled = 1; + + return 0; +} + +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) { + comedi_error(dev, "range/channel list is empty!"); + 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_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) +{ + unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE; + + outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG); + pcl816_ai_set_chan_range(dev, 0, 0); + pcl816_ai_clear_eoc(dev); + + /* Stop pacer */ + i8254_set_mode(timer_base, 0, 2, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 1, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 0, I8254_MODE0 | I8254_BINARY); + + /* set all digital outputs low */ + outb(0, dev->iobase + PCL816_DO_DI_LSB_REG); + outb(0, dev->iobase + PCL816_DO_DI_MSB_REG); +} + +static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl816_board *board = comedi_board(dev); + struct pcl816_private *devpriv; + struct comedi_subdevice *s; + int ret; + int i; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 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], pcl816_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 && (it->options[2] == 3 || it->options[2] == 1)) { + ret = request_dma(it->options[2], dev->board_name); + if (ret) { + dev_err(dev->class_dev, + "unable to request DMA channel %d\n", + it->options[2]); + return -EBUSY; + } + devpriv->dma = it->options[2]; + + devpriv->dmapages = 2; /* we need 16KB */ + devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE; + + for (i = 0; i < 2; i++) { + unsigned long dmabuf; + + dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages); + if (!dmabuf) + return -ENOMEM; + + devpriv->dmabuf[i] = dmabuf; + devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf); + } + } + + 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 (devpriv->dma) { + 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) +{ + struct pcl816_private *devpriv = dev->private; + + if (dev->private) { + pcl816_ai_cancel(dev, dev->read_subdev); + pcl816_reset(dev); + if (devpriv->dma) + free_dma(devpriv->dma); + if (devpriv->dmabuf[0]) + free_pages(devpriv->dmabuf[0], devpriv->dmapages); + if (devpriv->dmabuf[1]) + free_pages(devpriv->dmabuf[1], devpriv->dmapages); + } + 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 00000000000..7d00ae639d3 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl818.c @@ -0,0 +1,1258 @@ +/* + comedi/drivers/pcl818.c + + Author: Michal Dobes <dobes@tesnet.cz> + + hardware driver for Advantech cards: + card: PCL-818L, PCL-818H, PCL-818HD, PCL-818HG, PCL-818, PCL-718 + driver: pcl818l, pcl818h, pcl818hd, pcl818hg, pcl818, pcl718 +*/ +/* +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 <asm/dma.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "8253.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 { + unsigned int dma; /* used DMA, 0=don't use DMA */ + unsigned int dmapages; + unsigned int hwdmasize; + unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */ + unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */ + int next_dma_buf; /* which DMA buffer will be used next round */ + long dma_runs_to_end; /* how many we must permorm DMA transfer to end of record */ + unsigned long last_dma_run; /* how many bytes we must transfer on last DMA page */ + unsigned int ns_min; /* manimal allowed delay between samples (in us) for actual card */ + int i8253_osc_base; /* 1/frequency of on board oscilator in ns */ + int ai_act_scan; /* how many scans we finished */ + int ai_act_chan; /* actual position in actual scan */ + unsigned int act_chanlist[16]; /* MUX setting for actual AI operations */ + unsigned int act_chanlist_len; /* how long is actual MUX list */ + unsigned int act_chanlist_pos; /* actual position in MUX list */ + unsigned int ai_data_len; /* len of data buffer */ + unsigned int ao_readback[2]; + unsigned int divisor1; + unsigned int divisor2; + unsigned int usefifo:1; + unsigned int ai_cmd_running:1; + unsigned int ai_cmd_canceled:1; +}; + +static void pcl818_start_pacer(struct comedi_device *dev, bool load_counters) +{ + struct pcl818_private *devpriv = dev->private; + unsigned long timer_base = dev->iobase + PCL818_TIMER_BASE; + + i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); + udelay(1); + + if (load_counters) { + i8254_write(timer_base, 0, 2, devpriv->divisor2); + i8254_write(timer_base, 0, 1, devpriv->divisor1); + } +} + +static void pcl818_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int flags; + unsigned int bytes; + + disable_dma(devpriv->dma); /* disable dma */ + bytes = devpriv->hwdmasize; + if (cmd->stop_src == TRIG_COUNT) { + bytes = cmd->stop_arg * cfc_bytes_per_scan(s); + devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize; + devpriv->last_dma_run = bytes % devpriv->hwdmasize; + devpriv->dma_runs_to_end--; + if (devpriv->dma_runs_to_end >= 0) + bytes = devpriv->hwdmasize; + } + + devpriv->next_dma_buf = 0; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count(devpriv->dma, bytes); + release_dma_lock(flags); + enable_dma(devpriv->dma); +} + +static void pcl818_ai_setup_next_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + disable_dma(devpriv->dma); + devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; + if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) { + /* switch dma bufs */ + set_dma_mode(devpriv->dma, DMA_MODE_READ); + flags = claim_dma_lock(); + set_dma_addr(devpriv->dma, + devpriv->hwdmaptr[devpriv->next_dma_buf]); + if (devpriv->dma_runs_to_end || cmd->stop_src == TRIG_NONE) + set_dma_count(devpriv->dma, devpriv->hwdmasize); + else + set_dma_count(devpriv->dma, devpriv->last_dma_run); + release_dma_lock(flags); + enable_dma(devpriv->dma); + } + + devpriv->dma_runs_to_end--; +} + +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_dropout(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan) +{ + struct pcl818_private *devpriv = dev->private; + 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_EOA | COMEDI_CB_ERROR; + return true; + } + return false; +} + +static bool pcl818_ai_next_chan(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + s->async->events |= COMEDI_CB_BLOCK; + + devpriv->act_chanlist_pos++; + if (devpriv->act_chanlist_pos >= devpriv->act_chanlist_len) + devpriv->act_chanlist_pos = 0; + + s->async->cur_chan++; + if (s->async->cur_chan >= cmd->chanlist_len) { + s->async->cur_chan = 0; + devpriv->ai_act_scan--; + s->async->events |= COMEDI_CB_EOS; + } + + if (cmd->stop_src == TRIG_COUNT && devpriv->ai_act_scan == 0) { + /* all data sampled */ + 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)) { + comedi_error(dev, "A/D mode1/3 IRQ without DRDY!"); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + return; + } + + val = pcl818_ai_get_sample(dev, s, &chan); + + if (pcl818_ai_dropout(dev, s, chan)) + return; + + comedi_buf_put(s, val); + + pcl818_ai_next_chan(dev, s); +} + +static void pcl818_handle_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + unsigned short *ptr; + unsigned int chan; + unsigned int val; + int i, len, bufptr; + + pcl818_ai_setup_next_dma(dev, s); + + ptr = (unsigned short *)devpriv->dmabuf[1 - devpriv->next_dma_buf]; + + len = devpriv->hwdmasize >> 1; + bufptr = 0; + + for (i = 0; i < len; i++) { + val = ptr[bufptr++]; + chan = val & 0xf; + val = (val >> 4) & s->maxdata; + + if (pcl818_ai_dropout(dev, s, chan)) + break; + + comedi_buf_put(s, val); + + if (!pcl818_ai_next_chan(dev, s)) + 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) { + comedi_error(dev, "A/D mode1/3 FIFO overflow!"); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + return; + } + + if (status & 1) { + comedi_error(dev, "A/D mode1/3 FIFO interrupt without data!"); + s->async->events |= COMEDI_CB_EOA | 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_dropout(dev, s, chan)) + break; + + comedi_buf_put(s, val); + + if (!pcl818_ai_next_chan(dev, s)) + 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; + + 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. + */ + devpriv->ai_act_scan = 0; + 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); + + cfc_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) { + comedi_error(dev, "range/channel list is empty!"); + 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 = comedi_board(dev); + struct pcl818_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + board->ns_min); + else /* TRIG_EXT */ + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_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; + i8253_cascade_ns_to_timer(devpriv->i8253_osc_base, + &devpriv->divisor1, + &devpriv->divisor2, + &arg, cmd->flags); + err |= cfc_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_cmd *cmd = &s->async->cmd; + unsigned int ctrl = 0; + unsigned int seglen; + + if (devpriv->ai_cmd_running) + return -EBUSY; + + pcl818_start_pacer(dev, false); + + 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_data_len = s->async->prealloc_bufsz; + devpriv->ai_act_scan = cmd->stop_arg; + devpriv->ai_act_chan = 0; + devpriv->ai_cmd_running = 1; + devpriv->ai_cmd_canceled = 0; + devpriv->act_chanlist_pos = 0; + devpriv->dma_runs_to_end = 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 (devpriv->dma) { + pcl818_ai_setup_dma(dev, s); + + 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) + pcl818_start_pacer(dev, true); + + return 0; +} + +static int pcl818_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + if (!devpriv->ai_cmd_running) + return 0; + + if (devpriv->dma) { + if (cmd->stop_src == TRIG_NONE || + (cmd->stop_src == TRIG_COUNT && devpriv->ai_act_scan > 0)) { + if (!devpriv->ai_cmd_canceled) { + /* + * Wait for running dma transfer to end, + * do cleanup in interrupt. + */ + devpriv->ai_cmd_canceled = 1; + return 0; + } + } + disable_dma(devpriv->dma); + } + + outb(PCL818_CTRL_DISABLE_TRIG, dev->iobase + PCL818_CTRL_REG); + pcl818_start_pacer(dev, 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) +{ + struct pcl818_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + devpriv->ao_readback[chan] = data[i]; + outb((data[i] & 0x000f) << 4, + dev->iobase + PCL818_AO_LSB_REG(chan)); + outb((data[i] & 0x0ff0) >> 4, + dev->iobase + PCL818_AO_MSB_REG(chan)); + } + + return insn->n; +} + +static int pcl818_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcl818_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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 = comedi_board(dev); + unsigned long timer_base = dev->iobase + PCL818_TIMER_BASE; + 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); + i8254_set_mode(timer_base, 0, 2, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 1, I8254_MODE0 | I8254_BINARY); + i8254_set_mode(timer_base, 0, 0, I8254_MODE0 | I8254_BINARY); + + /* 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 = comedi_board(dev); + + /* 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 int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl818_board *board = comedi_board(dev); + struct pcl818_private *devpriv; + struct comedi_subdevice *s; + int ret; + int i; + + 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 && + (it->options[2] == 3 || it->options[2] == 1)) { + ret = request_dma(it->options[2], dev->board_name); + if (ret) { + dev_err(dev->class_dev, + "unable to request DMA channel %d\n", + it->options[2]); + return -EBUSY; + } + devpriv->dma = it->options[2]; + + devpriv->dmapages = 2; /* we need 16KB */ + devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE; + + for (i = 0; i < 2; i++) { + unsigned long dmabuf; + + dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages); + if (!dmabuf) + return -ENOMEM; + + devpriv->dmabuf[i] = dmabuf; + devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf); + } + } + + 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; + s->insn_read = pcl818_ao_insn_read; + s->insn_write = pcl818_ao_insn_write; + 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; + } + } 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; + + /* select 1/10MHz oscilator */ + if ((it->options[3] == 0) || (it->options[3] == 10)) + devpriv->i8253_osc_base = I8254_OSC_BASE_10MHZ; + else + devpriv->i8253_osc_base = I8254_OSC_BASE_1MHZ; + + /* max sampling speed */ + devpriv->ns_min = board->ns_min; + + if (!board->is_818) { + if ((it->options[6] == 1) || (it->options[6] == 100)) + devpriv->ns_min = 10000; /* extended PCL718 to 100kHz DAC */ + } + + 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); + if (devpriv->dma) + free_dma(devpriv->dma); + if (devpriv->dmabuf[0]) + free_pages(devpriv->dmabuf[0], devpriv->dmapages); + if (devpriv->dmabuf[1]) + free_pages(devpriv->dmabuf[1], devpriv->dmapages); + } + 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 00000000000..53e73737a90 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcm3724.c @@ -0,0 +1,247 @@ +/* + 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 PCM3724_SIZE 16 +#define SIZE_8255 4 + +#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 + +/* from 8255.c */ +#define CR_CW 0x80 +#define _8255_CR 3 +#define CR_B_IO 0x02 +#define CR_B_MODE 0x04 +#define CR_C_IO 0x09 +#define CR_A_IO 0x10 +#define CR_A_MODE(a) ((a)<<5) +#define CR_CW 0x80 + +/* used to track configured dios */ +struct priv_pcm3724 { + int dio_1; + int dio_2; +}; + +static int subdev_8255_cb(int dir, int port, int data, unsigned long arg) +{ + unsigned long iobase = arg; + unsigned char inbres; + if (dir) { + outb(data, iobase + port); + return 0; + } else { + inbres = inb(iobase + port); + return inbres; + } +} + +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 = CR_CW; + buffer_config = 0; + + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= CR_A_IO; + + if (!(s->io_bits & 0x00ff00)) + config |= CR_B_IO; + + if (!(s->io_bits & 0xff0000)) + config |= CR_C_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 + _8255_CR; + else + port_8255_cfg = dev->iobase + SIZE_8255 + _8255_CR; + + 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], PCM3724_SIZE); + 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, subdev_8255_cb, + dev->iobase + SIZE_8255 * i); + 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 00000000000..87c61d9b11d --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmad.c @@ -0,0 +1,159 @@ +/* + * 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] + * (Winsystems) 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 = comedi_board(dev); + 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 00000000000..1c7a135c91d --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmda12.c @@ -0,0 +1,176 @@ +/* + * 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 { + unsigned int ao_readback[8]; + 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 = devpriv->ao_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); + } + devpriv->ao_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; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + /* + * 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); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return insn->n; +} + +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; + + 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 00000000000..fed7e77e030 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmmio.c @@ -0,0 +1,849 @@ +/* + * 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" + +#include "comedi_fc.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 stop_count; + unsigned int active:1; + + unsigned int ao_readback[8]; +}; + +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 oldevents = s->async->events; + 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); + } + + /* Write the scan to the buffer. */ + if (comedi_buf_put(s, val) && + comedi_buf_put(s, val >> 16)) { + s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); + } else { + /* Overflow! Stop acquisition!! */ + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmmio_stop_intr(dev, s); + } + + /* Check for end of acquisition. */ + if (cmd->stop_src == TRIG_COUNT && devpriv->stop_count > 0) { + devpriv->stop_count--; + if (devpriv->stop_count == 0) { + s->async->events |= COMEDI_CB_EOA; + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmmio_stop_intr(dev, s); + } + } + +done: + spin_unlock_irqrestore(&devpriv->spinlock, flags); + + if (oldevents != s->async->events) + comedi_event(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 int 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; + + if (cmd->stop_src == TRIG_COUNT && devpriv->stop_count == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + devpriv->active = 0; + return 1; + } + + 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); + + return 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; + int event = 0; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&devpriv->spinlock, flags); + s->async->inttrig = NULL; + if (devpriv->active) + event = pcmmio_start_intr(dev, s); + spin_unlock_irqrestore(&devpriv->spinlock, flags); + + if (event) + comedi_event(dev, s); + + 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; + int event = 0; + + spin_lock_irqsave(&devpriv->spinlock, flags); + devpriv->active = 1; + + /* Set up end of acquisition. */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->stop_count = cmd->stop_arg; + else /* TRIG_NONE */ + devpriv->stop_count = 0; + + /* Set up start of acquisition. */ + if (cmd->start_src == TRIG_INT) + s->async->inttrig = pcmmio_inttrig_start_intr; + else /* TRIG_NOW */ + event = pcmmio_start_intr(dev, s); + + spin_unlock_irqrestore(&devpriv->spinlock, flags); + + if (event) + comedi_event(dev, s); + + 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* any count allowed */ + break; + case TRIG_NONE: + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + break; + default: + break; + } + + 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_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcmmio_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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) +{ + struct pcmmio_private *devpriv = dev->private; + unsigned long iobase = dev->iobase; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val = devpriv->ao_readback[chan]; + 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++) { + 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; + + devpriv->ao_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_read = pcmmio_ao_insn_read; + s->insn_write = pcmmio_ao_insn_write; + + /* 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; + 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 00000000000..62914bb342d --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmuio.c @@ -0,0 +1,690 @@ +/* + * 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] + * (Winsystems) 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" + +#include "comedi_fc.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 stop_count; + unsigned int active:1; + unsigned int continuous: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 = comedi_board(dev); + 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 oldevents = s->async->events; + 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); + } + + /* Write the scan to the buffer. */ + if (comedi_buf_put(s, val) && + comedi_buf_put(s, val >> 16)) { + s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); + } else { + /* Overflow! Stop acquisition!! */ + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmuio_stop_intr(dev, s); + } + + /* Check for end of acquisition. */ + if (!chip->continuous) { + /* stop_src == TRIG_COUNT */ + if (chip->stop_count > 0) { + chip->stop_count--; + if (chip->stop_count == 0) { + s->async->events |= COMEDI_CB_EOA; + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmuio_stop_intr(dev, s); + } + } + } + +done: + spin_unlock_irqrestore(&chip->spinlock, flags); + + if (oldevents != s->async->events) + comedi_event(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 int 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; + + if (!chip->continuous && chip->stop_count == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + chip->active = 0; + return 1; + } + + 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); + + return 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; + int event = 0; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&chip->spinlock, flags); + s->async->inttrig = NULL; + if (chip->active) + event = pcmuio_start_intr(dev, s); + + spin_unlock_irqrestore(&chip->spinlock, flags); + + if (event) + comedi_event(dev, s); + + 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; + int event = 0; + + spin_lock_irqsave(&chip->spinlock, flags); + chip->active = 1; + + /* Set up end of acquisition. */ + switch (cmd->stop_src) { + case TRIG_COUNT: + chip->continuous = 0; + chip->stop_count = cmd->stop_arg; + break; + default: + /* TRIG_NONE */ + chip->continuous = 1; + chip->stop_count = 0; + break; + } + + /* Set up start of acquisition. */ + if (cmd->start_src == TRIG_INT) + s->async->inttrig = pcmuio_inttrig_start_intr; + else /* TRIG_NOW */ + event = pcmuio_start_intr(dev, s); + + spin_unlock_irqrestore(&chip->spinlock, flags); + + if (event) + comedi_event(dev, s); + + 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* any count allowed */ + break; + case TRIG_NONE: + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + break; + default: + break; + } + + 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 = comedi_board(dev); + 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 suppport interrupts */ + if ((i == 0 && dev->irq) || (i == 2 && devpriv->irq2)) { + /* setup the interrupt subdevice */ + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + 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 00000000000..fbcf2506980 --- /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 00000000000..25706531b88 --- /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 00000000000..b3bbec0a0d2 --- /dev/null +++ b/drivers/staging/comedi/drivers/quatech_daqp_cs.c @@ -0,0 +1,816 @@ +/*====================================================================== + + 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 "../comedidev.h" +#include <linux/semaphore.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> + +#include <linux/completion.h> + +#include "comedi_fc.h" + +struct daqp_private { + int stop; + + enum { semaphore, buffer } interrupt_mode; + + struct completion eos; + + int count; +}; + +/* 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; + 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_EOA | 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_put(s, data); + + /* If there's a limit, decrement it + * and stop conversion if zero + */ + + if (devpriv->count > 0) { + devpriv->count--; + if (devpriv->count == 0) { + 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_EOA | COMEDI_CB_ERROR; + } + + s->async->events |= COMEDI_CB_BLOCK; + + cfc_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, int round) +{ + 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SPEED 10000 /* 100 kHz - in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) + err |= cfc_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 |= cfc_check_trigger_arg_min(&cmd->convert_arg, MAX_SPEED); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + else /* TRIG_NONE */ + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_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 & TRIG_ROUND_MASK); + 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 & TRIG_ROUND_MASK); + 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) { + devpriv->count = cmd->stop_arg * cmd->scan_end_arg; + threshold = 2 * devpriv->count; + while (threshold > DAQP_FIFO_SIZE * 3 / 4) + threshold /= 2; + } else { + devpriv->count = -1; + 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); + unsigned int val; + 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++) { + val = data[0]; + 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_WRITEABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &range_bipolar5; + s->insn_write = daqp_ao_insn_write; + + 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_WRITEABLE; + 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 00000000000..d55c5893203 --- /dev/null +++ b/drivers/staging/comedi/drivers/rtd520.c @@ -0,0 +1,1426 @@ +/* + * 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] + * (Real Time Devices) DM7520HR-8 [DM7520] + * (Real Time Devices) PCI4520 [PCI4520] + * (Real Time Devices) PCI4520-8 [PCI4520] + * 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/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "comedi_fc.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 *las0; + 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 */ + DECLARE_BITMAP(chan_is_bipolar, RTD_MAX_CHANLIST); + unsigned int ao_readback[2]; + 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, + int round_mode, int base) +{ + int divider; + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_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, int round_mode) +{ + return rtd_ns_to_timer_base(ns, round_mode, 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 = comedi_board(dev); + struct rtd_private *devpriv = dev->private; + 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; + __set_bit(index, devpriv->chan_is_bipolar); + } else if (range < board->range_uni10) { + /* +-10 range */ + r |= 0x100; + r |= ((range - board->range_bip10) & 0x7) << 4; + __set_bit(index, devpriv->chan_is_bipolar); + } else { + /* +10 range */ + r |= 0x200; + r |= ((range - board->range_uni10) & 0x7) << 4; + __clear_bit(index, devpriv->chan_is_bipolar); + } + + 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) +{ + struct rtd_private *devpriv = dev->private; + + if (n_chan > 1) { /* setup channel gain table */ + int ii; + + writel(0, devpriv->las0 + LAS0_CGT_CLEAR); + writel(1, devpriv->las0 + LAS0_CGT_ENABLE); + for (ii = 0; ii < n_chan; ii++) { + writel(rtd_convert_chan_gain(dev, list[ii], ii), + devpriv->las0 + LAS0_CGT_WRITE); + } + } else { /* just use the channel gain latch */ + writel(0, devpriv->las0 + LAS0_CGT_ENABLE); + writel(rtd_convert_chan_gain(dev, list[0], 0), + devpriv->las0 + 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) +{ + struct rtd_private *devpriv = dev->private; + unsigned int chanspec = CR_PACK(0, 0, AREF_GROUND); + unsigned i; + static const unsigned limit = 0x2000; + unsigned fifo_size = 0; + + writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR); + rtd_load_channelgain_list(dev, 1, &chanspec); + /* ADC conversion trigger source: SOFTWARE */ + writel(0, devpriv->las0 + LAS0_ADC_CONVERSION); + /* convert samples */ + for (i = 0; i < limit; ++i) { + unsigned fifo_status; + /* trigger conversion */ + writew(0, devpriv->las0 + LAS0_ADC); + udelay(1); + fifo_status = readl(devpriv->las0 + 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, devpriv->las0 + 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) +{ + struct rtd_private *devpriv = dev->private; + unsigned int status; + + status = readl(devpriv->las0 + 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; + int ret; + int n; + + /* clear any old fifo data */ + writel(0, devpriv->las0 + 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, devpriv->las0 + LAS0_ADC_CONVERSION); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + unsigned short d; + /* trigger conversion */ + writew(0, devpriv->las0 + 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 = d >> 3; /* low 3 bits are marker lines */ + if (test_bit(0, devpriv->chan_is_bipolar)) + /* convert to comedi unsigned data */ + 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; + int ii; + + for (ii = 0; ii < count; ii++) { + 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 = d >> 3; /* low 3 bits are marker lines */ + if (test_bit(s->async->cur_chan, devpriv->chan_is_bipolar)) + /* convert to comedi unsigned data */ + d = comedi_offset_munge(s, d); + d &= s->maxdata; + + if (!comedi_buf_put(s, d)) + return -1; + + if (devpriv->ai_count > 0) /* < 0, means read forever */ + devpriv->ai_count--; + } + return 0; +} + +/* + unknown amout of data is waiting in fifo. +*/ +static int ai_read_dregs(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct rtd_private *devpriv = dev->private; + + while (readl(devpriv->las0 + LAS0_ADC) & FS_ADC_NOT_EMPTY) { + unsigned short d = readw(devpriv->las1 + LAS1_ADC_FIFO); + + if (0 == devpriv->ai_count) { /* done */ + continue; /* read rest */ + } + + d = d >> 3; /* low 3 bits are marker lines */ + if (test_bit(s->async->cur_chan, devpriv->chan_is_bipolar)) + /* convert to comedi unsigned data */ + d = comedi_offset_munge(s, d); + d &= s->maxdata; + + if (!comedi_buf_put(s, d)) + 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(devpriv->las0 + 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(devpriv->las0 + 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; + + comedi_event(dev, s); + } 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; + + comedi_event(dev, s); + } + } + } + + overrun = readl(devpriv->las0 + LAS0_OVERRUN) & 0xffff; + if (overrun) + goto xfer_abort; + + /* clear the interrupt */ + writew(status, devpriv->las0 + LAS0_CLEAR); + readw(devpriv->las0 + LAS0_CLEAR); + return IRQ_HANDLED; + +xfer_abort: + writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR); + s->async->events |= COMEDI_CB_ERROR; + devpriv->ai_count = 0; /* stop and don't transfer any more */ + /* fall into xfer_done */ + +xfer_done: + /* pacer stop source: SOFTWARE */ + writel(0, devpriv->las0 + LAS0_PACER_STOP); + writel(0, devpriv->las0 + LAS0_PACER); /* stop pacer */ + writel(0, devpriv->las0 + LAS0_ADC_CONVERSION); + writew(0, devpriv->las0 + LAS0_IT); + + if (devpriv->ai_count > 0) { /* there shouldn't be anything left */ + fifo_status = readl(devpriv->las0 + LAS0_ADC); + ai_read_dregs(dev, s); /* read anything left in FIFO */ + } + + s->async->events |= COMEDI_CB_EOA; /* signal end to comedi */ + comedi_event(dev, s); + + /* clear the interrupt */ + status = readw(devpriv->las0 + LAS0_IT); + writew(status, devpriv->las0 + LAS0_CLEAR); + readw(devpriv->las0 + LAS0_CLEAR); + + fifo_status = readl(devpriv->las0 + LAS0_ADC); + overrun = readl(devpriv->las0 + LAS0_OVERRUN) & 0xffff; + + 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_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 (cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + RTD_MAX_SPEED_1)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_UP); + err |= -EINVAL; + } + if (cfc_check_trigger_arg_max(&cmd->scan_begin_arg, + RTD_MIN_SPEED_1)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_DOWN); + err |= -EINVAL; + } + } else { + if (cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + RTD_MAX_SPEED)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_UP); + err |= -EINVAL; + } + if (cfc_check_trigger_arg_max(&cmd->scan_begin_arg, + RTD_MIN_SPEED)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_DOWN); + err |= -EINVAL; + } + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); + } + + if (cmd->convert_src == TRIG_TIMER) { + if (1 == cmd->chanlist_len) { /* no scanning */ + if (cfc_check_trigger_arg_min(&cmd->convert_arg, + RTD_MAX_SPEED_1)) { + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_UP); + err |= -EINVAL; + } + if (cfc_check_trigger_arg_max(&cmd->convert_arg, + RTD_MIN_SPEED_1)) { + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_DOWN); + err |= -EINVAL; + } + } else { + if (cfc_check_trigger_arg_min(&cmd->convert_arg, + RTD_MAX_SPEED)) { + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_UP); + err |= -EINVAL; + } + if (cfc_check_trigger_arg_max(&cmd->convert_arg, + RTD_MIN_SPEED)) { + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_DOWN); + err |= -EINVAL; + } + } + } else { + /* external trigger */ + /* see above */ + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, 9); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* TODO check for rounding error due to counter wrap */ + } else { + /* TRIG_NONE */ + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= cfc_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, devpriv->las0 + LAS0_PACER_STOP); + writel(0, devpriv->las0 + LAS0_PACER); /* stop pacer */ + writel(0, devpriv->las0 + LAS0_ADC_CONVERSION); + writew(0, devpriv->las0 + LAS0_IT); + writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR); + writel(0, devpriv->las0 + 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, devpriv->las0 + LAS0_PACER_START); + /* burst trigger source: PACER */ + writel(1, devpriv->las0 + LAS0_BURST_START); + /* ADC conversion trigger source: BURST */ + writel(2, devpriv->las0 + LAS0_ADC_CONVERSION); + } else { /* single channel */ + /* pacer start source: SOFTWARE */ + writel(0, devpriv->las0 + LAS0_PACER_START); + /* ADC conversion trigger source: PACER */ + writel(1, devpriv->las0 + LAS0_ADC_CONVERSION); + } + writel((devpriv->fifosz / 2 - 1) & 0xffff, devpriv->las0 + 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 & TRIG_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, + devpriv->las0 + 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, devpriv->las0 + LAS0_PACER_SELECT); + /* just interrupt, don't stop */ + writel(1, devpriv->las0 + 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, + TRIG_ROUND_NEAREST); + /* set PACER clock */ + writel(timer & 0xffffff, devpriv->las0 + LAS0_PCLK); + + break; + + case TRIG_EXT: + /* pacer start source: EXTERNAL */ + writel(1, devpriv->las0 + 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, + TRIG_ROUND_NEAREST); + /* setup BURST clock */ + writel(timer & 0x3ff, devpriv->las0 + LAS0_BCLK); + } + + break; + + case TRIG_EXT: /* external */ + /* burst trigger source: EXTERNAL */ + writel(2, devpriv->las0 + 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, devpriv->las0 + LAS0_CLEAR); + readw(devpriv->las0 + LAS0_CLEAR); + + /* TODO: allow multiple interrupt sources */ + if (devpriv->xfer_count > 0) { /* transfer every N samples */ + writew(IRQM_ADC_ABOUT_CNT, devpriv->las0 + LAS0_IT); + } else { /* 1/2 FIFO transfers */ + writew(IRQM_ADC_ABOUT_CNT, devpriv->las0 + LAS0_IT); + } + + /* BUG: start_src is ASSUMED to be TRIG_NOW */ + /* BUG? it seems like things are running before the "start" */ + readl(devpriv->las0 + 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; + u32 overrun; + u16 status; + + /* pacer stop source: SOFTWARE */ + writel(0, devpriv->las0 + LAS0_PACER_STOP); + writel(0, devpriv->las0 + LAS0_PACER); /* stop pacer */ + writel(0, devpriv->las0 + LAS0_ADC_CONVERSION); + writew(0, devpriv->las0 + LAS0_IT); + devpriv->ai_count = 0; /* stop and don't transfer any more */ + status = readw(devpriv->las0 + LAS0_IT); + overrun = readl(devpriv->las0 + LAS0_OVERRUN) & 0xffff; + return 0; +} + +static int rtd_ao_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + struct rtd_private *devpriv = dev->private; + 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(devpriv->las0 + 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, devpriv->las0 + + ((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, devpriv->las0 + + ((chan == 0) ? LAS0_DAC1 : LAS0_DAC2)); + + devpriv->ao_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; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int rtd_ao_rinsn(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); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + + return i; +} + +static int rtd_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct rtd_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) + writew(s->state & 0xff, devpriv->las0 + LAS0_DIO0); + + data[1] = readw(devpriv->las0 + 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) +{ + struct rtd_private *devpriv = dev->private; + 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, devpriv->las0 + LAS0_DIO_STATUS); + writew(s->io_bits & 0xff, devpriv->las0 + LAS0_DIO0_CTRL); + + /* clear interrupts */ + writew(0x00, devpriv->las0 + 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, devpriv->las0 + LAS0_BOARD_RESET); + udelay(100); /* needed? */ + writel(0, devpriv->lcfg + PLX_INTRCS_REG); + writew(0, devpriv->las0 + LAS0_IT); + writew(~0, devpriv->las0 + LAS0_CLEAR); + readw(devpriv->las0 + LAS0_CLEAR); +} + +/* + * initialize board, per RTD spec + * also, initialize shadow registers + */ +static void rtd_init_board(struct comedi_device *dev) +{ + struct rtd_private *devpriv = dev->private; + + rtd_reset(dev); + + writel(0, devpriv->las0 + LAS0_OVERRUN); + writel(0, devpriv->las0 + LAS0_CGT_CLEAR); + writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR); + writel(0, devpriv->las0 + LAS0_DAC1_RESET); + writel(0, devpriv->las0 + LAS0_DAC2_RESET); + /* clear digital IO fifo */ + writew(0, devpriv->las0 + LAS0_DIO_STATUS); + writeb((0 << 6) | 0x30, devpriv->las0 + LAS0_UTC_CTRL); + writeb((1 << 6) | 0x30, devpriv->las0 + LAS0_UTC_CTRL); + writeb((2 << 6) | 0x30, devpriv->las0 + LAS0_UTC_CTRL); + writeb((3 << 6) | 0x00, devpriv->las0 + 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; + + devpriv->las0 = pci_ioremap_bar(pcidev, 2); + devpriv->las1 = pci_ioremap_bar(pcidev, 3); + devpriv->lcfg = pci_ioremap_bar(pcidev, 0); + if (!devpriv->las0 || !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; + s->insn_read = rtd_ao_rinsn; + + 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 (devpriv->las0 && devpriv->lcfg) + rtd_reset(dev); + if (dev->irq) { + writel(readl(devpriv->lcfg + PLX_INTRCS_REG) & + ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E), + devpriv->lcfg + PLX_INTRCS_REG); + free_irq(dev->irq, dev); + } + if (devpriv->las0) + iounmap(devpriv->las0); + 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 00000000000..bd447b2add7 --- /dev/null +++ b/drivers/staging/comedi/drivers/rti800.c @@ -0,0 +1,378 @@ +/* + * 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] + * (Analog Devices) 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 + +#define RTI800_IOSIZE 0x10 + +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 int ao_readback[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_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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + 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 val = devpriv->ao_readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + if (devpriv->dac_2comp[chan]) + val ^= 0x800; + + outb(val & 0xff, dev->iobase + reg_lo); + outb((val >> 8) & 0xff, dev->iobase + reg_hi); + } + + devpriv->ao_readback[chan] = val; + + 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 = comedi_board(dev); + struct rti800_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], RTI800_IOSIZE); + 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->insn_read = rti800_ao_insn_read; + s->insn_write = rti800_ao_insn_write; + 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; + } 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 00000000000..605a31d702e --- /dev/null +++ b/drivers/staging/comedi/drivers/rti802.c @@ -0,0 +1,143 @@ +/* + * 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]; + unsigned int ao_readback[8]; +}; + +static int rti802_ao_insn_read(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; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return insn->n; +} + +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); + unsigned int val; + int i; + + outb(chan, dev->iobase + RTI802_SELECT); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + + devpriv->ao_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_read = rti802_ao_insn_read; + s->insn_write = rti802_ao_insn_write; + 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 00000000000..85d2b7a3c12 --- /dev/null +++ b/drivers/staging/comedi/drivers/s526.c @@ -0,0 +1,628 @@ +/* + 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_SIZE 64 + +#define S526_START_AI_CONV 0 +#define S526_AI_READ 0 + +/* Ports */ +#define S526_IOSIZE 0x40 +#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 ao_readback[2]; + 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; + break; + } + + 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_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 short val; + int i; + + val = chan << 1; + outw(val, dev->iobase + REG_DAC); + + for (i = 0; i < insn->n; i++) { + outw(data[i], dev->iobase + REG_ADD); + devpriv->ao_readback[chan] = data[i]; + /* starts the D/A conversion */ + outw(val + 1, dev->iobase + REG_DAC); + } + + return i; +} + +static int s526_ao_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 i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +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], S526_IOSIZE); + 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_winsn; + s->insn_read = s526_ao_rinsn; + + 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 00000000000..0838f8aa695 --- /dev/null +++ b/drivers/staging/comedi/drivers/s626.c @@ -0,0 +1,3110 @@ +/* + * 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/pci.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/types.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" +#include "s626.h" + +struct s626_buffer_dma { + dma_addr_t physical_base; + void *logical_base; +}; + +struct s626_private { + void __iomem *mmio; + uint8_t ai_cmd_running; /* ai_cmd is running */ + uint8_t ai_continuous; /* continuous acquisition */ + int ai_sample_count; /* number of samples to acquire */ + 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) */ + unsigned int ao_readback[S626_DAC_CHANNELS]; +}; + +/* COUNTER OBJECT ------------------------------------------------ */ +struct s626_enc_info { + /* Pointers to functions that differ for A and B counters: */ + /* Return clock enable. */ + uint16_t (*get_enable)(struct comedi_device *dev, + const struct s626_enc_info *k); + /* Return interrupt source. */ + uint16_t (*get_int_src)(struct comedi_device *dev, + const struct s626_enc_info *k); + /* Return preload trigger source. */ + uint16_t (*get_load_trig)(struct comedi_device *dev, + const struct s626_enc_info *k); + /* Return standardized operating mode. */ + uint16_t (*get_mode)(struct comedi_device *dev, + const struct s626_enc_info *k); + /* Generate soft index strobe. */ + void (*pulse_index)(struct comedi_device *dev, + const struct s626_enc_info *k); + /* Program clock enable. */ + void (*set_enable)(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t enab); + /* Program interrupt source. */ + void (*set_int_src)(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t int_source); + /* Program preload trigger source. */ + void (*set_load_trig)(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t trig); + /* Program standardized operating mode. */ + void (*set_mode)(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t setup, + uint16_t disable_int_src); + /* Reset event capture flags. */ + void (*reset_cap_flags)(struct comedi_device *dev, + const struct s626_enc_info *k); + + uint16_t my_cra; /* address of CRA register */ + uint16_t my_crb; /* address of CRB register */ + uint16_t my_latch_lsw; /* address of Latch least-significant-word + * register */ + uint16_t my_event_bits[4]; /* bit translations for IntSrc -->RDMISC2 */ +}; + +/* 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))) +#define S626_EVBITS(C) { 0, S626_OVERMASK(C), S626_INDXMASK(C), \ + S626_OVERMASK(C) | S626_INDXMASK(C) } + +/* + * Translation table to map IntSrc into equivalent RDMISC2 event flag bits. + * static const uint16_t s626_event_bits[][4] = + * { S626_EVBITS(0), S626_EVBITS(1), S626_EVBITS(2), S626_EVBITS(3), + * S626_EVBITS(4), S626_EVBITS(5) }; + */ + +/* + * 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) +{ + struct s626_private *devpriv = dev->private; + unsigned int val = (cmd << 16) | cmd; + + mmiowb(); + writel(val, devpriv->mmio + reg); +} + +static void s626_mc_disable(struct comedi_device *dev, + unsigned int cmd, unsigned int reg) +{ + struct s626_private *devpriv = dev->private; + + writel(cmd << 16 , devpriv->mmio + reg); + mmiowb(); +} + +static bool s626_mc_test(struct comedi_device *dev, + unsigned int cmd, unsigned int reg) +{ + struct s626_private *devpriv = dev->private; + unsigned int val; + + val = readl(devpriv->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) +{ + struct s626_private *devpriv = dev->private; + 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) + comedi_error(dev, + "Timeout while uploading to DEBI control register."); + + /* Wait until DEBI transfer is done */ + for (i = 0; i < timeout; i++) { + if (!(readl(devpriv->mmio + S626_P_PSR) & S626_PSR_DEBI_S)) + break; + udelay(1); + } + if (i == timeout) + comedi_error(dev, "DEBI transfer timeout."); +} + +/* + * Read a value from a gate array register. + */ +static uint16_t s626_debi_read(struct comedi_device *dev, uint16_t addr) +{ + struct s626_private *devpriv = dev->private; + + /* Set up DEBI control register value in shadow RAM */ + writel(S626_DEBI_CMD_RDWORD | addr, devpriv->mmio + S626_P_DEBICMD); + + /* Execute the DEBI transfer. */ + s626_debi_transfer(dev); + + return readl(devpriv->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) +{ + struct s626_private *devpriv = dev->private; + + /* Set up DEBI control register value in shadow RAM */ + writel(S626_DEBI_CMD_WRWORD | addr, devpriv->mmio + S626_P_DEBICMD); + writel(wdata, devpriv->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) +{ + struct s626_private *devpriv = dev->private; + unsigned int val; + + addr &= 0xffff; + writel(S626_DEBI_CMD_RDWORD | addr, devpriv->mmio + S626_P_DEBICMD); + s626_debi_transfer(dev); + + writel(S626_DEBI_CMD_WRWORD | addr, devpriv->mmio + S626_P_DEBICMD); + val = readl(devpriv->mmio + S626_P_DEBIAD); + val &= mask; + val |= wdata; + writel(val & 0xffff, devpriv->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) +{ + struct s626_private *devpriv = dev->private; + unsigned int ctrl; + int ret; + + /* Write I2C command to I2C Transfer Control shadow register */ + writel(val, devpriv->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(devpriv->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(devpriv->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) +{ + struct s626_private *devpriv = dev->private; + unsigned int status; + + switch (context) { + case s626_send_dac_wait_not_mc1_a2out: + status = readl(devpriv->mmio + S626_P_MC1); + if (!(status & S626_MC1_A2OUT)) + return 0; + break; + case s626_send_dac_wait_ssr_af2_out: + status = readl(devpriv->mmio + S626_P_SSR); + if (status & S626_SSR_AF2_OUT) + return 0; + break; + case s626_send_dac_wait_fb_buffer2_msb_00: + status = readl(devpriv->mmio + S626_P_FB_BUFFER2); + if (!(status & 0xff000000)) + return 0; + break; + case s626_send_dac_wait_fb_buffer2_msb_ff: + status = readl(devpriv->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, devpriv->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, devpriv->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) { + comedi_error(dev, "DMA transfer timeout."); + 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, + devpriv->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) { + comedi_error(dev, "TSL timeout waiting for slot 1 to execute."); + 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, + devpriv->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(devpriv->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) { + comedi_error(dev, + "TSL timeout waiting for slot 0 to execute."); + 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, + devpriv->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) { + comedi_error(dev, "TSL timeout waiting for slot 0 to execute."); + 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, + devpriv->mmio + S626_VECTPORT(2)); + /* Slot 3: Transmit low data byte to target DAC */ + writel(S626_XSD2 | S626_XFIFO_0 | ws_image, + devpriv->mmio + S626_VECTPORT(3)); + /* Slot 4: Transmit to non-existent TrimDac channel to keep clock */ + writel(S626_XSD2 | S626_XFIFO_3 | S626_WS3, + devpriv->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, + devpriv->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, + devpriv->mmio + S626_VECTPORT(2)); + /* Slot 3: Send low uint8_t to target TrimDac */ + writel(S626_XSD2 | S626_XFIFO_0 | S626_WS3, + devpriv->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, + devpriv->mmio + S626_VECTPORT(4)); + /* Slot 5: Send NOP low uint8_t to DAC0 */ + writel(S626_XSD2 | S626_XFIFO_2 | S626_WS1 | S626_EOS, + devpriv->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. + */ + +/* + * Read a counter's output latch. + */ +static uint32_t s626_read_latch(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + uint32_t value; + + /* Latch counts and fetch LSW of latched counts value. */ + value = s626_debi_read(dev, k->my_latch_lsw); + + /* Fetch MSW of latched counts and combine with LSW. */ + value |= ((uint32_t)s626_debi_read(dev, k->my_latch_lsw + 2) << 16); + + /* Return latched counts. */ + return value; +} + +/* + * 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, + const struct s626_enc_info *k, uint16_t value) +{ + s626_debi_replace(dev, k->my_crb, + ~(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, + const struct s626_enc_info *k, uint32_t value) +{ + s626_debi_write(dev, k->my_latch_lsw, value); + s626_debi_write(dev, k->my_latch_lsw + 2, value >> 16); +} + +/* ****** PRIVATE COUNTER FUNCTIONS ****** */ + +/* + * Reset a counter's index and overflow event capture flags. + */ +static void s626_reset_cap_flags_a(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + s626_debi_replace(dev, k->my_crb, ~S626_CRBMSK_INTCTRL, + (S626_SET_CRB_INTRESETCMD(1) | + S626_SET_CRB_INTRESET_A(1))); +} + +static void s626_reset_cap_flags_b(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + s626_debi_replace(dev, k->my_crb, ~S626_CRBMSK_INTCTRL, + (S626_SET_CRB_INTRESETCMD(1) | + S626_SET_CRB_INTRESET_B(1))); +} + +/* + * 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, + const struct s626_enc_info *k) +{ + 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, k->my_cra); + crb = s626_debi_read(dev, k->my_crb); + + /* + * 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, + const struct s626_enc_info *k) +{ + 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, k->my_cra); + crb = s626_debi_read(dev, k->my_crb); + + /* + * 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; +} + +/* + * 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, + const struct s626_enc_info *k, 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 &= ~k->my_event_bits[3]; + + /* + * While retaining CounterB and LatchSrc configurations, program the + * new counter operating mode. + */ + s626_debi_replace(dev, k->my_cra, + S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B, cra); + s626_debi_replace(dev, k->my_crb, + ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_A), crb); +} + +static void s626_set_mode_b(struct comedi_device *dev, + const struct s626_enc_info *k, 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 &= ~k->my_event_bits[3]; + + /* + * While retaining CounterA and LatchSrc configurations, program the + * new counter operating mode. + */ + s626_debi_replace(dev, k->my_cra, + ~(S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B), cra); + s626_debi_replace(dev, k->my_crb, + S626_CRBMSK_CLKENAB_A | S626_CRBMSK_LATCHSRC, crb); +} + +/* + * Return/set a counter's enable. enab: 0=always enabled, 1=enabled by index. + */ +static void s626_set_enable_a(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t enab) +{ + s626_debi_replace(dev, k->my_crb, + ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_A), + S626_SET_CRB_CLKENAB_A(enab)); +} + +static void s626_set_enable_b(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t enab) +{ + s626_debi_replace(dev, k->my_crb, + ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_B), + S626_SET_CRB_CLKENAB_B(enab)); +} + +static uint16_t s626_get_enable_a(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_CRB_CLKENAB_A(s626_debi_read(dev, k->my_crb)); +} + +static uint16_t s626_get_enable_b(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_CRB_CLKENAB_B(s626_debi_read(dev, k->my_crb)); +} + +#ifdef unused +static uint16_t s626_get_latch_source(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_CRB_LATCHSRC(s626_debi_read(dev, k->my_crb)); +} +#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_a(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t trig) +{ + s626_debi_replace(dev, k->my_cra, ~S626_CRAMSK_LOADSRC_A, + S626_SET_CRA_LOADSRC_A(trig)); +} + +static void s626_set_load_trig_b(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t trig) +{ + s626_debi_replace(dev, k->my_crb, + ~(S626_CRBMSK_LOADSRC_B | S626_CRBMSK_INTCTRL), + S626_SET_CRB_LOADSRC_B(trig)); +} + +static uint16_t s626_get_load_trig_a(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_CRA_LOADSRC_A(s626_debi_read(dev, k->my_cra)); +} + +static uint16_t s626_get_load_trig_b(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_CRB_LOADSRC_B(s626_debi_read(dev, k->my_crb)); +} + +/* + * 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_a(struct comedi_device *dev, + const struct s626_enc_info *k, + uint16_t int_source) +{ + struct s626_private *devpriv = dev->private; + + /* Reset any pending counter overflow or index captures. */ + s626_debi_replace(dev, k->my_crb, ~S626_CRBMSK_INTCTRL, + (S626_SET_CRB_INTRESETCMD(1) | + S626_SET_CRB_INTRESET_A(1))); + + /* Program counter interrupt source. */ + s626_debi_replace(dev, k->my_cra, ~S626_CRAMSK_INTSRC_A, + S626_SET_CRA_INTSRC_A(int_source)); + + /* Update MISC2 interrupt enable mask. */ + devpriv->counter_int_enabs = + (devpriv->counter_int_enabs & ~k->my_event_bits[3]) | + k->my_event_bits[int_source]; +} + +static void s626_set_int_src_b(struct comedi_device *dev, + const struct s626_enc_info *k, + uint16_t int_source) +{ + struct s626_private *devpriv = dev->private; + uint16_t crb; + + /* Cache writeable CRB register image. */ + crb = s626_debi_read(dev, k->my_crb) & ~S626_CRBMSK_INTCTRL; + + /* Reset any pending counter overflow or index captures. */ + s626_debi_write(dev, k->my_crb, (crb | S626_SET_CRB_INTRESETCMD(1) | + S626_SET_CRB_INTRESET_B(1))); + + /* Program counter interrupt source. */ + s626_debi_write(dev, k->my_crb, ((crb & ~S626_CRBMSK_INTSRC_B) | + S626_SET_CRB_INTSRC_B(int_source))); + + /* Update MISC2 interrupt enable mask. */ + devpriv->counter_int_enabs = + (devpriv->counter_int_enabs & ~k->my_event_bits[3]) | + k->my_event_bits[int_source]; +} + +static uint16_t s626_get_int_src_a(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_CRA_INTSRC_A(s626_debi_read(dev, k->my_cra)); +} + +static uint16_t s626_get_int_src_b(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_CRB_INTSRC_B(s626_debi_read(dev, k->my_crb)); +} + +#ifdef unused +/* + * Return/set the clock multiplier. + */ +static void s626_set_clk_mult(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t value) +{ + k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_CLKMULT) | + S626_SET_STD_CLKMULT(value)), false); +} + +static uint16_t s626_get_clk_mult(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_STD_CLKMULT(k->get_mode(dev, k)); +} + +/* + * Return/set the clock polarity. + */ +static void s626_set_clk_pol(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t value) +{ + k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_CLKPOL) | + S626_SET_STD_CLKPOL(value)), false); +} + +static uint16_t s626_get_clk_pol(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_STD_CLKPOL(k->get_mode(dev, k)); +} + +/* + * Return/set the encoder mode. + */ +static void s626_set_enc_mode(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t value) +{ + k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_ENCMODE) | + S626_SET_STD_ENCMODE(value)), false); +} + +static uint16_t s626_get_enc_mode(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_STD_ENCMODE(k->get_mode(dev, k)); +} + +/* + * Return/set the index polarity. + */ +static void s626_set_index_pol(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t value) +{ + k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_INDXPOL) | + S626_SET_STD_INDXPOL(value != 0)), false); +} + +static uint16_t s626_get_index_pol(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_STD_INDXPOL(k->get_mode(dev, k)); +} + +/* + * Return/set the index source. + */ +static void s626_set_index_src(struct comedi_device *dev, + const struct s626_enc_info *k, uint16_t value) +{ + k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_INDXSRC) | + S626_SET_STD_INDXSRC(value != 0)), false); +} + +static uint16_t s626_get_index_src(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + return S626_GET_STD_INDXSRC(k->get_mode(dev, k)); +} +#endif + +/* + * Generate an index pulse. + */ +static void s626_pulse_index_a(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + uint16_t cra; + + cra = s626_debi_read(dev, k->my_cra); + /* Pulse index. */ + s626_debi_write(dev, k->my_cra, (cra ^ S626_CRAMSK_INDXPOL_A)); + s626_debi_write(dev, k->my_cra, cra); +} + +static void s626_pulse_index_b(struct comedi_device *dev, + const struct s626_enc_info *k) +{ + uint16_t crb; + + crb = s626_debi_read(dev, k->my_crb) & ~S626_CRBMSK_INTCTRL; + /* Pulse index. */ + s626_debi_write(dev, k->my_crb, (crb ^ S626_CRBMSK_INDXPOL_B)); + s626_debi_write(dev, k->my_crb, crb); +} + +static const struct s626_enc_info s626_enc_chan_info[] = { + { + .get_enable = s626_get_enable_a, + .get_int_src = s626_get_int_src_a, + .get_load_trig = s626_get_load_trig_a, + .get_mode = s626_get_mode_a, + .pulse_index = s626_pulse_index_a, + .set_enable = s626_set_enable_a, + .set_int_src = s626_set_int_src_a, + .set_load_trig = s626_set_load_trig_a, + .set_mode = s626_set_mode_a, + .reset_cap_flags = s626_reset_cap_flags_a, + .my_cra = S626_LP_CR0A, + .my_crb = S626_LP_CR0B, + .my_latch_lsw = S626_LP_CNTR0ALSW, + .my_event_bits = S626_EVBITS(0), + }, { + .get_enable = s626_get_enable_a, + .get_int_src = s626_get_int_src_a, + .get_load_trig = s626_get_load_trig_a, + .get_mode = s626_get_mode_a, + .pulse_index = s626_pulse_index_a, + .set_enable = s626_set_enable_a, + .set_int_src = s626_set_int_src_a, + .set_load_trig = s626_set_load_trig_a, + .set_mode = s626_set_mode_a, + .reset_cap_flags = s626_reset_cap_flags_a, + .my_cra = S626_LP_CR1A, + .my_crb = S626_LP_CR1B, + .my_latch_lsw = S626_LP_CNTR1ALSW, + .my_event_bits = S626_EVBITS(1), + }, { + .get_enable = s626_get_enable_a, + .get_int_src = s626_get_int_src_a, + .get_load_trig = s626_get_load_trig_a, + .get_mode = s626_get_mode_a, + .pulse_index = s626_pulse_index_a, + .set_enable = s626_set_enable_a, + .set_int_src = s626_set_int_src_a, + .set_load_trig = s626_set_load_trig_a, + .set_mode = s626_set_mode_a, + .reset_cap_flags = s626_reset_cap_flags_a, + .my_cra = S626_LP_CR2A, + .my_crb = S626_LP_CR2B, + .my_latch_lsw = S626_LP_CNTR2ALSW, + .my_event_bits = S626_EVBITS(2), + }, { + .get_enable = s626_get_enable_b, + .get_int_src = s626_get_int_src_b, + .get_load_trig = s626_get_load_trig_b, + .get_mode = s626_get_mode_b, + .pulse_index = s626_pulse_index_b, + .set_enable = s626_set_enable_b, + .set_int_src = s626_set_int_src_b, + .set_load_trig = s626_set_load_trig_b, + .set_mode = s626_set_mode_b, + .reset_cap_flags = s626_reset_cap_flags_b, + .my_cra = S626_LP_CR0A, + .my_crb = S626_LP_CR0B, + .my_latch_lsw = S626_LP_CNTR0BLSW, + .my_event_bits = S626_EVBITS(3), + }, { + .get_enable = s626_get_enable_b, + .get_int_src = s626_get_int_src_b, + .get_load_trig = s626_get_load_trig_b, + .get_mode = s626_get_mode_b, + .pulse_index = s626_pulse_index_b, + .set_enable = s626_set_enable_b, + .set_int_src = s626_set_int_src_b, + .set_load_trig = s626_set_load_trig_b, + .set_mode = s626_set_mode_b, + .reset_cap_flags = s626_reset_cap_flags_b, + .my_cra = S626_LP_CR1A, + .my_crb = S626_LP_CR1B, + .my_latch_lsw = S626_LP_CNTR1BLSW, + .my_event_bits = S626_EVBITS(4), + }, { + .get_enable = s626_get_enable_b, + .get_int_src = s626_get_int_src_b, + .get_load_trig = s626_get_load_trig_b, + .get_mode = s626_get_mode_b, + .pulse_index = s626_pulse_index_b, + .set_enable = s626_set_enable_b, + .set_int_src = s626_set_int_src_b, + .set_load_trig = s626_set_load_trig_b, + .set_mode = s626_set_mode_b, + .reset_cap_flags = s626_reset_cap_flags_b, + .my_cra = S626_LP_CR2A, + .my_crb = S626_LP_CR2B, + .my_latch_lsw = S626_LP_CNTR2BLSW, + .my_event_bits = S626_EVBITS(5), + }, +}; + +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) { + const struct s626_enc_info *k = + &s626_enc_chan_info[5]; + + devpriv->ai_convert_count = cmd->chanlist_len; + k->set_enable(dev, k, 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++) { + irqbit = 0; + /* 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; + const struct s626_enc_info *k; + uint16_t irqbit; + + /* read interrupt type */ + irqbit = s626_debi_read(dev, S626_LP_RDMISC2); + + /* check interrupt on counters */ + if (irqbit & S626_IRQ_COINT1A) { + k = &s626_enc_chan_info[0]; + + /* clear interrupt capture flag */ + k->reset_cap_flags(dev, k); + } + if (irqbit & S626_IRQ_COINT2A) { + k = &s626_enc_chan_info[1]; + + /* clear interrupt capture flag */ + k->reset_cap_flags(dev, k); + } + if (irqbit & S626_IRQ_COINT3A) { + k = &s626_enc_chan_info[2]; + + /* clear interrupt capture flag */ + k->reset_cap_flags(dev, k); + } + if (irqbit & S626_IRQ_COINT1B) { + k = &s626_enc_chan_info[3]; + + /* clear interrupt capture flag */ + k->reset_cap_flags(dev, k); + } + if (irqbit & S626_IRQ_COINT2B) { + k = &s626_enc_chan_info[4]; + + /* clear interrupt capture flag */ + k->reset_cap_flags(dev, k); + + if (devpriv->ai_convert_count > 0) { + devpriv->ai_convert_count--; + if (devpriv->ai_convert_count == 0) + k->set_enable(dev, k, 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) { + k = &s626_enc_chan_info[5]; + + /* clear interrupt capture flag */ + k->reset_cap_flags(dev, k); + + 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) { + k = &s626_enc_chan_info[4]; + devpriv->ai_convert_count = cmd->chanlist_len; + k->set_enable(dev, k, 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; + bool finished = false; + 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++; + + /* put data into read buffer */ + cfc_write_to_buffer(s, tempdata); + } + + /* end of scan occurs */ + async->events |= COMEDI_CB_EOS; + + if (!devpriv->ai_continuous) + devpriv->ai_sample_count--; + if (devpriv->ai_sample_count <= 0) { + devpriv->ai_cmd_running = 0; + + /* Stop RPS program */ + s626_mc_disable(dev, S626_MC1_ERPS1, S626_P_MC1); + + /* send end of acquisition */ + async->events |= COMEDI_CB_EOA; + + /* disable master interrupt */ + finished = true; + } + + if (devpriv->ai_cmd_running && cmd->scan_begin_src == TRIG_EXT) + s626_dio_set_irq(dev, cmd->scan_begin_arg); + + /* tell comedi that data is there */ + comedi_event(dev, s); + + return finished; +} + +static irqreturn_t s626_irq_handler(int irq, void *d) +{ + struct comedi_device *dev = d; + struct s626_private *devpriv = dev->private; + 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(devpriv->mmio + S626_P_IER); + + /* read interrupt type */ + irqtype = readl(devpriv->mmio + S626_P_ISR); + + /* disable master interrupt */ + writel(0, devpriv->mmio + S626_P_IER); + + /* clear interrupt */ + writel(irqtype, devpriv->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, devpriv->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, + devpriv->mmio + S626_P_RPSADDR1); + + /* Construct RPS program in rps_buf DMA buffer */ + if (cmd != NULL && 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 != NULL && 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) +{ + struct s626_private *devpriv = dev->private; + unsigned int status; + + status = readl(devpriv->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) +{ + struct s626_private *devpriv = dev->private; + 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(devpriv->mmio + S626_P_GPIO); + /* Assert ADC Start command */ + writel(gpio_image & ~S626_GPIO1_HI, + devpriv->mmio + S626_P_GPIO); + /* and stretch it out */ + writel(gpio_image & ~S626_GPIO1_HI, + devpriv->mmio + S626_P_GPIO); + writel(gpio_image & ~S626_GPIO1_HI, + devpriv->mmio + S626_P_GPIO); + /* Negate ADC Start command */ + writel(gpio_image | S626_GPIO1_HI, devpriv->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(devpriv->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(devpriv->mmio + S626_P_GPIO); + /* Assert ADC Start command */ + writel(gpio_image & ~S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO); + /* and stretch it out */ + writel(gpio_image & ~S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO); + writel(gpio_image & ~S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO); + /* Negate ADC Start command */ + writel(gpio_image | S626_GPIO1_HI, devpriv->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(devpriv->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, int round_mode) +{ + int divider, base; + + base = 500; /* 2MHz internal clock */ + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + + *nanosec = base * divider; + return divider - 1; +} + +static void s626_timer_load(struct comedi_device *dev, + const struct s626_enc_info *k, 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; */ + + k->set_mode(dev, k, setup, false); + + /* Set the preload register */ + s626_preload(dev, k, tick); + + /* + * Software index pulse forces the preload register to load + * into the counter + */ + k->set_load_trig(dev, k, 0); + k->pulse_index(dev, k); + + /* set reload on counter overflow */ + k->set_load_trig(dev, k, 1); + + /* set interrupt on overflow */ + k->set_int_src(dev, k, S626_INTSRC_OVER); + + s626_set_latch_source(dev, k, value_latchsrc); + /* k->set_enable(dev, k, (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; + const struct s626_enc_info *k; + 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, devpriv->mmio + S626_P_IER); + + /* clear interrupt request */ + writel(S626_IRQ_RPS1 | S626_IRQ_GPIO3, devpriv->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; + + /* test if cmd is valid */ + if (cmd == NULL) + return -EINVAL; + + 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 + */ + k = &s626_enc_chan_info[5]; + tick = s626_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* load timer value and enable interrupt */ + s626_timer_load(dev, k, tick); + k->set_enable(dev, k, 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 + */ + k = &s626_enc_chan_info[4]; + tick = s626_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* load timer value and enable interrupt */ + s626_timer_load(dev, k, tick); + k->set_enable(dev, k, 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; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* data arrives as one packet */ + devpriv->ai_sample_count = cmd->stop_arg; + devpriv->ai_continuous = 0; + break; + case TRIG_NONE: + /* continuous acquisition */ + devpriv->ai_continuous = 1; + devpriv->ai_sample_count = 1; + 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, devpriv->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 |= cfc_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_INT | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT | TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + err |= cfc_check_trigger_arg_max(&cmd->start_arg, 39); + break; + } + + if (cmd->scan_begin_src == TRIG_EXT) + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 39); + if (cmd->convert_src == TRIG_EXT) + err |= cfc_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 |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + S626_MAX_SPEED); + err |= cfc_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 |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); */ + } + if (cmd->convert_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + S626_MAX_SPEED); + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, + S626_MIN_SPEED); + } else { + /* external trigger */ + /* see above */ + /* err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); */ + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + else /* TRIG_NONE */ + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_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 & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= cfc_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, devpriv->mmio + S626_P_IER); + + devpriv->ai_cmd_running = 0; + + return 0; +} + +static int s626_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct s626_private *devpriv = dev->private; + int i; + int ret; + uint16_t chan = CR_CHAN(insn->chanspec); + int16_t dacdata; + + for (i = 0; i < insn->n; i++) { + dacdata = (int16_t) data[i]; + devpriv->ao_readback[CR_CHAN(insn->chanspec)] = data[i]; + dacdata -= (0x1fff); + + ret = s626_set_dac(dev, chan, dacdata); + if (ret) + return ret; + } + + return i; +} + +static int s626_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct s626_private *devpriv = dev->private; + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + + return i; +} + +/* *************** 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) +{ + 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; + const struct s626_enc_info *k = + &s626_enc_chan_info[CR_CHAN(insn->chanspec)]; + + /* (data==NULL) ? (Preloadvalue=0) : (Preloadvalue=data[0]); */ + + k->set_mode(dev, k, setup, true); + s626_preload(dev, k, data[0]); + k->pulse_index(dev, k); + s626_set_latch_source(dev, k, value_latchsrc); + k->set_enable(dev, k, (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) +{ + int n; + const struct s626_enc_info *k = + &s626_enc_chan_info[CR_CHAN(insn->chanspec)]; + + for (n = 0; n < insn->n; n++) + data[n] = s626_read_latch(dev, k); + + return n; +} + +static int s626_enc_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct s626_enc_info *k = + &s626_enc_chan_info[CR_CHAN(insn->chanspec)]; + + /* Set the preload register */ + s626_preload(dev, k, data[0]); + + /* + * Software index pulse forces the preload register to load + * into the counter + */ + k->set_load_trig(dev, k, 0); + k->pulse_index(dev, k); + k->set_load_trig(dev, k, 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_close_dma_b(struct comedi_device *dev, + struct s626_buffer_dma *pdma, size_t bsize) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + void *vbptr; + dma_addr_t vpptr; + + if (pdma == NULL) + return; + + /* find the matching allocation from the board struct */ + vbptr = pdma->logical_base; + vpptr = pdma->physical_base; + if (vbptr) { + pci_free_consistent(pcidev, bsize, vbptr, vpptr); + pdma->logical_base = NULL; + pdma->physical_base = 0; + } +} + +static void s626_counters_init(struct comedi_device *dev) +{ + int chan; + const struct s626_enc_info *k; + 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++) { + k = &s626_enc_chan_info[chan]; + k->set_mode(dev, k, setup, true); + k->set_int_src(dev, k, 0); + k->reset_cap_flags(dev, k); + k->set_enable(dev, k, 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 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, devpriv->mmio + S626_P_DEBICFG); + + /* Disable MMU paging */ + writel(S626_DEBI_PAGE_DISABLE, devpriv->mmio + S626_P_DEBIPAGE); + + /* Init GPIO so that ADC Start* is negated */ + writel(S626_GPIO_BASE | S626_GPIO1_HI, devpriv->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, + devpriv->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, devpriv->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, devpriv->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, devpriv->mmio + S626_P_TSL1); + writel(S626_RSD1 | S626_SIB_A1 | S626_EOS, + devpriv->mmio + S626_P_TSL1 + 4); + + /* Enable TSL1 slot list so that it executes all the time */ + writel(S626_ACON1_ADCSTART, devpriv->mmio + S626_P_ACON1); + + /* + * Initialize RPS registers used for ADC + */ + + /* Physical start of RPS program */ + writel((uint32_t)devpriv->rps_buf.physical_base, + devpriv->mmio + S626_P_RPSADDR1); + /* RPS program performs no explicit mem writes */ + writel(0, devpriv->mmio + S626_P_RPSPAGE1); + /* Disable RPS timeouts */ + writel(0, devpriv->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, devpriv->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, devpriv->mmio + S626_P_BASEA2_OUT); + writel((uint32_t)(phys_buf + sizeof(uint32_t)), + devpriv->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, devpriv->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, + devpriv->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, devpriv->mmio + S626_VECTPORT(1)); + + /* Start DAC's audio interface (TSL2) running */ + writel(S626_ACON1_DACSTART, devpriv->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; + + devpriv->mmio = pci_ioremap_bar(pcidev, 0); + if (!devpriv->mmio) + return -ENOMEM; + + /* disable master interrupt */ + writel(0, devpriv->mmio + S626_P_IER); + + /* soft reset */ + writel(S626_MC1_SOFT_RESET, devpriv->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_winsn; + s->insn_read = s626_ao_rinsn; + + 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 (devpriv->mmio) { + /* interrupt mask */ + /* Disable master interrupt */ + writel(0, devpriv->mmio + S626_P_IER); + /* Clear board's IRQ status flag */ + writel(S626_IRQ_GPIO3 | S626_IRQ_RPS1, + devpriv->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, devpriv->mmio + S626_P_MC1); + writel(S626_ACON1_BASE, devpriv->mmio + S626_P_ACON1); + + s626_close_dma_b(dev, &devpriv->rps_buf, + S626_DMABUF_SIZE); + s626_close_dma_b(dev, &devpriv->ana_buf, + S626_DMABUF_SIZE); + } + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv->mmio) + iounmap(devpriv->mmio); + } + comedi_pci_disable(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 00000000000..33b72739c1c --- /dev/null +++ b/drivers/staging/comedi/drivers/s626.h @@ -0,0 +1,774 @@ +/* + * 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): */ +#define S626_LP_CR0A 0x0000 /* 0A setup register. */ +#define S626_LP_CR0B 0x0002 /* 0B setup register. */ +#define S626_LP_CR1A 0x0004 /* 1A setup register. */ +#define S626_LP_CR1B 0x0006 /* 1B setup register. */ +#define S626_LP_CR2A 0x0008 /* 2A setup register. */ +#define S626_LP_CR2B 0x000A /* 2B setup register. */ + +/* Counter PreLoad (write) and Latch (read) Registers: */ +#define S626_LP_CNTR0ALSW 0x000C /* 0A lsw. */ +#define S626_LP_CNTR0AMSW 0x000E /* 0A msw. */ +#define S626_LP_CNTR0BLSW 0x0010 /* 0B lsw. */ +#define S626_LP_CNTR0BMSW 0x0012 /* 0B msw. */ +#define S626_LP_CNTR1ALSW 0x0014 /* 1A lsw. */ +#define S626_LP_CNTR1AMSW 0x0016 /* 1A msw. */ +#define S626_LP_CNTR1BLSW 0x0018 /* 1B lsw. */ +#define S626_LP_CNTR1BMSW 0x001A /* 1B msw. */ +#define S626_LP_CNTR2ALSW 0x001C /* 2A lsw. */ +#define S626_LP_CNTR2AMSW 0x001E /* 2A msw. */ +#define S626_LP_CNTR2BLSW 0x0020 /* 2B lsw. */ +#define S626_LP_CNTR2BMSW 0x0022 /* 2B msw. */ + +/* 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 00000000000..441813ffb17 --- /dev/null +++ b/drivers/staging/comedi/drivers/serial2002.c @@ -0,0 +1,807 @@ +/* + 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; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + f->f_pos = 0; + result = f->f_op->write(f, p, count, &f->f_pos); + set_fs(oldfs); + return result; +} + +static int serial2002_tty_readb(struct file *f, unsigned char *buf) +{ + char __user *p = (__force char __user *)buf; + + f->f_pos = 0; + return f->f_op->read(f, p, 1, &f->f_pos); +} + +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; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + if (f->f_op->poll) { + serial2002_tty_read_poll_wait(f, timeout); + + if (serial2002_tty_readb(f, &ch) == 1) + result = ch; + } else { + /* Device does not support poll, busy wait */ + int retries = 0; + while (1) { + retries++; + if (retries >= timeout) + break; + + if (serial2002_tty_readb(f, &ch) == 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(sizeof(unsigned int) * s->n_chan, 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(sizeof(struct serial2002_range_table_t) * + s->n_chan, 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; + + data = serial2002_read(devpriv->tty, 1000); + if (data.kind != is_channel || data.index != 31 || + S2002_CFG_KIND(data.value) == S2002_CFG_KIND_INVALID) { + break; + } else { + int channel = S2002_CFG_CHAN(data.value); + int range = S2002_CFG_BASE(data.value); + + switch (S2002_CFG_KIND(data.value)) { + 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 = S2002_CFG_KIND(data.value); + + switch (S2002_CFG_CMD(data.value)) { + 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 (S2002_CFG_CMD(data.value) == + 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_WRITEABLE; + 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_WRITEABLE; + 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/skel.c b/drivers/staging/comedi/drivers/skel.c new file mode 100644 index 00000000000..3bfa221faf4 --- /dev/null +++ b/drivers/staging/comedi/drivers/skel.c @@ -0,0 +1,729 @@ +/* + comedi/drivers/skel.c + Skeleton code for a 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: skel +Description: Skeleton driver, an example for driver writers +Devices: +Author: ds +Updated: Mon, 18 Mar 2002 15:34:01 -0800 +Status: works + +This driver is a documented example on how Comedi drivers are +written. + +Configuration Options: + none +*/ + +/* + * The previous block comment is used to automatically generate + * documentation in Comedi and Comedilib. The fields: + * + * Driver: the name of the driver + * Description: a short phrase describing the driver. Don't list boards. + * Devices: a full list of the boards that attempt to be supported by + * the driver. Format is "(manufacturer) board name [comedi name]", + * where comedi_name is the name that is used to configure the board. + * See the comment near board_name: in the struct comedi_driver structure + * below. If (manufacturer) or [comedi name] is missing, the previous + * value is used. + * Author: you + * Updated: date when the _documentation_ was last updated. Use 'date -R' + * to get a value for this. + * Status: a one-word description of the status. Valid values are: + * works - driver works correctly on most boards supported, and + * passes comedi_test. + * unknown - unknown. Usually put there by ds. + * experimental - may not work in any particular release. Author + * probably wants assistance testing it. + * bitrotten - driver has not been update in a long time, probably + * doesn't work, and probably is missing support for significant + * Comedi interface features. + * untested - author probably wrote it "blind", and is believed to + * work, but no confirmation. + * + * These headers should be followed by a blank line, and any comments + * you wish to say about the driver. The comment area is the place + * to put any known bugs, limitations, unsupported features, supported + * command triggers, whether or not commands are supported on particular + * subdevices, etc. + * + * Somewhere in the comment should be information about configuration + * options that are used with comedi_config. + */ + +#include <linux/module.h> +#include <linux/pci.h> + +#include "../comedidev.h" + +#include "comedi_fc.h" + +/* Imaginary registers for the imaginary board */ + +#define SKEL_SIZE 0 + +#define SKEL_START_AI_CONV 0 +#define SKEL_AI_READ 0 + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +enum skel_boardid { + BOARD_SKEL100, + BOARD_SKEL200, +}; + +struct skel_board { + const char *name; + int ai_chans; + int ai_bits; + int have_dio; +}; + +static const struct skel_board skel_boards[] = { + [BOARD_SKEL100] = { + .name = "skel-100", + .ai_chans = 16, + .ai_bits = 12, + .have_dio = 1, + }, + [BOARD_SKEL200] = { + .name = "skel-200", + .ai_chans = 8, + .ai_bits = 16, + }, +}; + +/* 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 skel_private { + + int data; + + /* Used for AO readback */ + unsigned int ao_readback[2]; +}; + +/* 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 skel_ns_to_timer(unsigned int *ns, int round) +{ + /* trivial timer */ + /* if your timing is done through two cascaded timers, the + * i8253_cascade_ns_to_timer() function in 8253.h can be + * very helpful. There are also i8254_load() and i8254_mm_load() + * which can be used to load values into the ubiquitous 8254 counters + */ + + return *ns; +} + +/* + * This function doesn't require a particular form, this is just + * what happens to be used in some of the drivers. The comedi_timeout() + * helper uses this callback to check for the end-of-conversion while + * waiting for up to 1 second. This function should return 0 when the + * conversion is finished and -EBUSY to keep waiting. Any other errno + * will terminate comedi_timeout() and return that errno to the caller. + * If the timeout occurs, comedi_timeout() will return -ETIMEDOUT. + */ +static int skel_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + /* status = inb(dev->iobase + SKEL_STATUS); */ + status = 1; + if (status) + return 0; + return -EBUSY; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int skel_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct skel_board *thisboard = comedi_board(dev); + int n; + unsigned int d; + int ret; + + /* a typical programming sequence */ + + /* write channel to multiplexer */ + /* outw(chan,dev->iobase + SKEL_MUX); */ + + /* don't wait for mux to settle */ + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + /* outw(0,dev->iobase + SKEL_CONVERT); */ + + /* wait for conversion to end */ + ret = comedi_timeout(dev, s, insn, skel_ai_eoc, 0); + if (ret) + return ret; + + /* read data */ + /* d = inw(dev->iobase + SKEL_AI_DATA); */ + d = 0; + + /* mangle the data as necessary */ + d ^= 1 << (thisboard->ai_bits - 1); + + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +/* + * 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 executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. + */ +static int skel_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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SPEED 10000 /* in nanoseconds */ +#define MIN_SPEED 1000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + MAX_SPEED); + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, + MIN_SPEED); + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); + } + + if (cmd->convert_src == TRIG_TIMER) { + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, MAX_SPEED); + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, MIN_SPEED); + } else { + /* external trigger */ + /* see above */ + err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + else /* TRIG_NONE */ + err |= cfc_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; + skel_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + skel_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + arg); + } + } + + if (err) + return 4; + + return 0; +} + +static int skel_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct skel_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + + /* 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++) { + /* a typical programming sequence */ + /* outw(data[i],dev->iobase + SKEL_DA0 + chan); */ + devpriv->ao_readback[chan] = data[i]; + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int skel_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct skel_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; +} + +/* + * DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write. + */ +static int skel_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + /* + * The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. + * + * The core provided comedi_dio_update_state() function can + * be used to handle the internal state update to DIO subdevices + * with <= 32 channels. This function will return '0' if the + * state does not change or the mask of the channels that need + * to be updated. + */ + if (comedi_dio_update_state(s, data)) { + /* Write out the new digital output lines */ + /* outw(s->state, dev->iobase + SKEL_DIO); */ + } + + /* + * On return, data[1] contains the value of the digital + * input and output lines. + */ + /* data[1] = inw(dev->iobase + SKEL_DIO); */ + + /* + * Or we could just return the software copy of the output + * values if it was a purely digital output subdevice. + */ + /* data[1] = s->state; */ + + return insn->n; +} + +static int skel_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + /* + * The input or output configuration of each digital line is + * configured by special insn_config instructions. + * + * chanspec contains the channel to be changed + * data[0] contains the instruction to perform on the channel + * + * Normally the core provided comedi_dio_insn_config() function + * can be used to handle the boilerplpate. + */ + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + /* Update the hardware to the new configuration */ + /* outw(s->io_bits, dev->iobase + SKEL_DIO_CONFIG); */ + + return insn->n; +} + +/* + * Handle common part of skel_attach() and skel_auto_attach(). + */ +static int skel_common_attach(struct comedi_device *dev) +{ + const struct skel_board *thisboard = comedi_board(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* dev->read_subdev=s; */ + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + /* we support single-ended (ground) and differential */ + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &range_bipolar10; + s->len_chanlist = 16; /* This is the maximum chanlist length that + the board can handle */ + s->insn_read = skel_ai_rinsn; +/* +* s->subdev_flags |= SDF_CMD_READ; +* s->do_cmd = skel_ai_cmd; +*/ + s->do_cmdtest = skel_ai_cmdtest; + + s = &dev->subdevices[1]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 1; + s->maxdata = 0xffff; + s->range_table = &range_bipolar5; + s->insn_write = skel_ao_winsn; + s->insn_read = skel_ao_rinsn; + + s = &dev->subdevices[2]; + /* digital i/o subdevice */ + if (thisboard->have_dio) { + 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 = skel_dio_insn_bits; + s->insn_config = skel_dio_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +/* + * _attach is called by the Comedi core to configure the driver + * for a particular board in response to the COMEDI_DEVCONFIG ioctl for + * a matching board or driver name. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that address. + * + * Drivers that handle only PCI or USB devices do not usually support + * manual attachment of those devices via the COMEDI_DEVCONFIG ioctl, so + * those drivers do not have an _attach function; they just have an + * _auto_attach function instead. (See skel_auto_attach() for an example + * of such a function.) + */ +static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct skel_board *thisboard; + struct skel_private *devpriv; + +/* + * If you can probe the device to determine what device in a series + * it is, this is the place to do it. Otherwise, dev->board_ptr + * should already be initialized. + */ + /* dev->board_ptr = skel_probe(dev, it); */ + + thisboard = comedi_board(dev); + + /* + * The dev->board_name is initialized by the comedi core before + * calling the (*attach) function. It can be optionally set by + * the driver if additional probing has been done. + */ + /* dev->board_name = thisboard->name; */ + + /* Allocate the private data */ + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + +/* + * Supported boards are usually either auto-attached via the + * Comedi driver's _auto_attach routine, or manually attached via the + * Comedi driver's _attach routine. In most cases, attempts to + * manual attach boards that are usually auto-attached should be + * rejected by this function. + */ +/* + * if (thisboard->bustype == pci_bustype) { + * dev_err(dev->class_dev, + * "Manual attachment of PCI board '%s' not supported\n", + * thisboard->name); + * } + */ + +/* + * For ISA boards, get the i/o base address from it->options[], + * request the i/o region and set dev->iobase * from it->options[]. + * If using interrupts, get the IRQ number from it->options[]. + */ + + /* + * Call a common function to handle the remaining things to do for + * attaching ISA or PCI boards. (Extra parameters could be added + * to pass additional information such as IRQ number.) + */ + return skel_common_attach(dev); +} + +/* + * _auto_attach is called via comedi_pci_auto_config() (or + * comedi_usb_auto_config(), etc.) to handle devices that can be attached + * to the Comedi core automatically without the COMEDI_DEVCONFIG ioctl. + * + * The context parameter is driver dependent. + */ +static int skel_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct skel_board *thisboard = NULL; + struct skel_private *devpriv; + int ret; + + /* Hack to allow unused code to be optimized out. */ + if (!IS_ENABLED(CONFIG_COMEDI_PCI_DRIVERS)) + return -EINVAL; + + /* + * In this example, the _auto_attach is for a PCI device. + * + * The 'context' passed to this function is the id->driver_data + * associated with the PCI device found in the id_table during + * the modprobe. This 'context' is the index of the entry in + * skel_boards[i] that contains the boardinfo for the PCI device. + */ + if (context < ARRAY_SIZE(skel_boards)) + thisboard = &skel_boards[context]; + if (!thisboard) + return -ENODEV; + + /* + * Point the struct comedi_device to the matching board info + * and set the board name. + */ + dev->board_ptr = thisboard; + dev->board_name = thisboard->name; + + /* Allocate the private data */ + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* Enable the PCI device. */ + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + /* + * Record the fact that the PCI device is enabled so that it can + * be disabled during _detach(). + * + * For this example driver, we assume PCI BAR 0 is the main I/O + * region for the board registers and use dev->iobase to hold the + * I/O base address and to indicate that the PCI device has been + * enabled. + * + * (For boards with memory-mapped registers, dev->iobase is not + * usually needed for register access, so can just be set to 1 + * to indicate that the PCI device has been enabled.) + */ + dev->iobase = pci_resource_start(pcidev, 0); + + /* + * Call a common function to handle the remaining things to do for + * attaching ISA or PCI boards. (Extra parameters could be added + * to pass additional information such as IRQ number.) + */ + return skel_common_attach(dev); +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static void skel_detach(struct comedi_device *dev) +{ + const struct skel_board *thisboard = comedi_board(dev); + struct skel_private *devpriv = dev->private; + + if (!thisboard || !devpriv) + return; + +/* + * Do common stuff such as freeing IRQ, unmapping remapped memory + * regions, etc., being careful to check that the stuff is valid given + * that _detach() is called even when _attach() or _auto_attach() return + * an error. + */ + + if (IS_ENABLED(CONFIG_COMEDI_PCI_DRIVERS) /* && + thisboard->bustype == pci_bustype */) { + /* + * PCI board + * + * If PCI device enabled by _auto_attach() (or _attach()), + * disable it here. + */ + comedi_pci_disable(dev); + } else { + /* + * ISA board + * + * Release the first I/O region requested during the + * _attach(). This is safe to call even if the request + * failed. If any additional I/O regions are requested + * they need to be released by the driver. + */ + comedi_legacy_detach(dev); + } +} + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static struct comedi_driver skel_driver = { + .driver_name = "dummy", + .module = THIS_MODULE, + .attach = skel_attach, + .auto_attach = skel_auto_attach, + .detach = skel_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in skel_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &skel_boards[0].name, + .offset = sizeof(struct skel_board), + .num_names = ARRAY_SIZE(skel_boards), +}; + +#ifdef CONFIG_COMEDI_PCI_DRIVERS + +static int skel_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &skel_driver, id->driver_data); +} + +/* + * Please add your PCI vendor ID to comedidev.h, and it will + * be forwarded upstream. + */ +#define PCI_VENDOR_ID_SKEL 0xdafe + +/* + * This is used by modprobe to translate PCI IDs to drivers. + * Should only be used for PCI and ISA-PnP devices + */ +static const struct pci_device_id skel_pci_table[] = { + { PCI_VDEVICE(SKEL, 0x0100), BOARD_SKEL100 }, + { PCI_VDEVICE(SKEL, 0x0200), BOARD_SKEL200 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, skel_pci_table); + +static struct pci_driver skel_pci_driver = { + .name = "dummy", + .id_table = skel_pci_table, + .probe = skel_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(skel_driver, skel_pci_driver); +#else +module_comedi_driver(skel_driver); +#endif + +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 00000000000..848c3080158 --- /dev/null +++ b/drivers/staging/comedi/drivers/ssv_dnp.c @@ -0,0 +1,187 @@ +/* + 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 00000000000..adf7cb7086c --- /dev/null +++ b/drivers/staging/comedi/drivers/unioxx5.c @@ -0,0 +1,508 @@ +/*************************************************************************** + * * + * 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 DRIVER_NAME "unioxx5" +#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 writting 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 writting 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 = DRIVER_NAME, + .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 00000000000..5f65e4213c6 --- /dev/null +++ b/drivers/staging/comedi/drivers/usbdux.c @@ -0,0 +1,1822 @@ +/* + comedi/drivers/usbdux.c + Copyright (C) 2003-2007 Bernd Porr, Bernd.Porr@f2s.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: usbdux +Description: University of Stirling USB DAQ & INCITE Technology Limited +Devices: [ITL] USB-DUX (usbdux.o) +Author: Bernd Porr <BerndPorr@f2s.com> +Updated: 8 Dec 2008 +Status: Stable +Configuration options: + You have to upload firmware with the -i option. The + firmware is usually installed under /usr/share/usb or + /usr/local/share/usb or /lib/firmware. + +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. + +Please 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/usb.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> + +#include "../comedidev.h" + +#include "comedi_fc.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 + +#define USBDUX_NUM_AO_CHAN 4 + +/* 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 */ + uint16_t *in_buf; + /* input buffer for single insn */ + uint16_t *insn_buf; + + unsigned int ao_readback[USBDUX_NUM_AO_CHAN]; + + unsigned int high_speed:1; + unsigned int ai_cmd_running:1; + unsigned int ao_cmd_running:1; + unsigned int pwm_cmd_running:1; + + /* number of samples to acquire */ + int ai_sample_count; + int ao_sample_count; + /* 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; +} + +/* analogue IN - interrupt service routine */ +static void usbduxsub_ai_isoc_irq(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct comedi_subdevice *s = dev->read_subdev; + struct usbdux_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int i, err; + + /* first we test if something unusual has just happened */ + switch (urb->status) { + case 0: + /* copy the result in the transfer buffer */ + memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF); + 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"); + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + if (devpriv->ai_cmd_running) { + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(dev, s); + /* stop the transfer w/o unlink */ + usbdux_ai_stop(dev, 0); + } + return; + + default: + /* a real error on the bus */ + /* pass error to comedi if we are really running a command */ + if (devpriv->ai_cmd_running) { + dev_err(dev->class_dev, + "Non-zero urb status received in ai intr context: %d\n", + urb->status); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(dev, s); + /* don't do an unlink here */ + usbdux_ai_stop(dev, 0); + } + return; + } + + /* + * at this point we are reasonably sure that nothing dodgy has happened + * are we running a command? + */ + if (unlikely(!devpriv->ai_cmd_running)) { + /* + * not running a command, do not continue execution if no + * asynchronous command is running in particular not resubmit + */ + return; + } + + urb->dev = comedi_to_usb_dev(dev); + + /* resubmit the urb */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + dev_err(dev->class_dev, + "urb resubmit failed in int-context! err=%d\n", err); + if (err == -EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handler!\n"); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(dev, s); + /* don't do an unlink here */ + usbdux_ai_stop(dev, 0); + return; + } + + devpriv->ai_counter--; + if (likely(devpriv->ai_counter > 0)) + return; + + /* timer zero, transfer measurements to comedi */ + devpriv->ai_counter = devpriv->ai_timer; + + /* test, if we transmit only a fixed number of samples */ + if (cmd->stop_src == TRIG_COUNT) { + /* not continuous, fixed number of samples */ + devpriv->ai_sample_count--; + /* all samples received? */ + if (devpriv->ai_sample_count < 0) { + /* prevent a resubmit next time */ + usbdux_ai_stop(dev, 0); + /* say comedi that the acquistion is over */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + } + /* 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 */ + err = comedi_buf_put(s, val); + if (unlikely(err == 0)) { + /* buffer overflow */ + usbdux_ai_stop(dev, 0); + return; + } + } + /* tell comedi that data is there */ + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + 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_isoc_irq(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct comedi_subdevice *s = dev->write_subdev; + struct usbdux_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + uint8_t *datap; + int ret; + int i; + + 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->ao_cmd_running) { + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + usbdux_ao_stop(dev, 0); + } + return; + + default: + /* a real error */ + if (devpriv->ao_cmd_running) { + dev_err(dev->class_dev, + "Non-zero urb status received in ao intr context: %d\n", + urb->status); + s->async->events |= COMEDI_CB_ERROR; + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + /* we do an unlink if we are in the high speed mode */ + usbdux_ao_stop(dev, 0); + } + return; + } + + /* are we actually running? */ + if (!devpriv->ao_cmd_running) + return; + + /* normal operation: executing a command in this subdevice */ + devpriv->ao_counter--; + if ((int)devpriv->ao_counter <= 0) { + /* timer zero */ + devpriv->ao_counter = devpriv->ao_timer; + + /* handle non continous acquisition */ + if (cmd->stop_src == TRIG_COUNT) { + /* fixed number of samples */ + devpriv->ao_sample_count--; + if (devpriv->ao_sample_count < 0) { + /* all samples transmitted */ + usbdux_ao_stop(dev, 0); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + /* no resubmit of the urb */ + 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; + + ret = comedi_buf_get(s, &val); + if (ret < 0) { + dev_err(dev->class_dev, "buffer underflow\n"); + s->async->events |= (COMEDI_CB_EOA | + COMEDI_CB_OVERFLOW); + } + /* pointer to the DA */ + *datap++ = val & 0xff; + *datap++ = (val >> 8) & 0xff; + *datap++ = chan << 6; + devpriv->ao_readback[chan] = val; + + s->async->events |= COMEDI_CB_BLOCK; + comedi_event(dev, s); + } + } + urb->transfer_buffer_length = SIZEOUTBUF; + urb->dev = comedi_to_usb_dev(dev); + urb->status = 0; + if (devpriv->ao_cmd_running) { + 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"); + + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(dev, s); + /* don't do an unlink here */ + usbdux_ao_stop(dev, 0); + } + } +} + +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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= cfc_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 |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, + tmp_timer); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + err |= cfc_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; + + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + + 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->stop_src == TRIG_COUNT) { + /* data arrives as one packet */ + devpriv->ai_sample_count = cmd->stop_arg; + } else { + /* continous acquisition */ + devpriv->ai_sample_count = 0; + } + + 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; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + down(&devpriv->sem); + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + up(&devpriv->sem); + + return insn->n; +} + +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 = devpriv->ao_readback[chan]; + uint16_t *p = (uint16_t *)&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; + } + devpriv->ao_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 |= cfc_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 |= cfc_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 |= cfc_check_trigger_src(&cmd->convert_src, flags); + + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + 1000000); + + /* not used now, is for later use */ + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 125000); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + err |= cfc_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; + + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + + /* 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->stop_src == TRIG_COUNT) { + /* not continuous */ + /* counter */ + /* high speed also scans everything at once */ + if (0) { /* (devpriv->high_speed) */ + devpriv->ao_sample_count = cmd->stop_arg * + cmd->scan_end_arg; + } else { + /* there's no scan as the scan has been */ + /* perf inside the FX2 */ + /* data arrives as one packet */ + devpriv->ao_sample_count = cmd->stop_arg; + } + } else { + /* continous acquisition */ + devpriv->ao_sample_count = 0; + } + + 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); + uint16_t *p = (uint16_t *)&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; + } else { + 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 = USBDUX_NUM_AO_CHAN; + 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; + + /* 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 00000000000..85f9dcf5940 --- /dev/null +++ b/drivers/staging/comedi/drivers/usbduxfast.c @@ -0,0 +1,1139 @@ +/* + * Copyright (C) 2004 Bernd Porr, Bernd.Porr@f2s.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. + */ + +/* + * 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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> +#include "comedi_fc.h" +#include "../comedidev.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 */ + long int ai_sample_count; /* number of samples to acquire */ + 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; +} + +/* + * analogue IN + * interrupt service routine + */ +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 comedi_cmd *cmd = &async->cmd; + struct usb_device *usb = comedi_to_usb_dev(dev); + struct usbduxfast_private *devpriv = dev->private; + int n, err; + + /* are we running a command? */ + if (unlikely(!devpriv->ai_cmd_running)) { + /* + * not running a command + * do not continue execution if no asynchronous command + * is running in particular not resubmit + */ + return; + } + + /* first we test if something unusual has just happened */ + switch (urb->status) { + case 0: + break; + + /* + * happens after an unlink command or when the device + * is plugged out + */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* tell this comedi */ + async->events |= COMEDI_CB_EOA; + async->events |= COMEDI_CB_ERROR; + comedi_event(dev, s); + /* stop the transfer w/o unlink */ + usbduxfast_ai_stop(dev, 0); + return; + + default: + pr_err("non-zero urb status received in ai intr context: %d\n", + urb->status); + async->events |= COMEDI_CB_EOA; + async->events |= COMEDI_CB_ERROR; + comedi_event(dev, s); + usbduxfast_ai_stop(dev, 0); + return; + } + + if (!devpriv->ignore) { + if (cmd->stop_src == TRIG_COUNT) { + /* not continuous, fixed number of samples */ + n = urb->actual_length / sizeof(uint16_t); + if (unlikely(devpriv->ai_sample_count < n)) { + unsigned int num_bytes; + + /* partial sample received */ + num_bytes = devpriv->ai_sample_count * + sizeof(uint16_t); + cfc_write_array_to_buffer(s, + urb->transfer_buffer, + num_bytes); + usbduxfast_ai_stop(dev, 0); + /* tell comedi that the acquistion is over */ + async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + devpriv->ai_sample_count -= n; + } + /* write the full buffer to comedi */ + err = cfc_write_array_to_buffer(s, urb->transfer_buffer, + urb->actual_length); + if (unlikely(err == 0)) { + /* buffer overflow */ + usbduxfast_ai_stop(dev, 0); + return; + } + + /* tell comedi that data is there */ + comedi_event(dev, s); + } else { + /* ignore this packet */ + devpriv->ignore--; + } + + /* + * command is still running + * resubmit urb for BULK transfer + */ + urb->dev = usb; + urb->status = 0; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + dev_err(dev->class_dev, + "urb resubm failed: %d", err); + async->events |= COMEDI_CB_EOA; + async->events |= COMEDI_CB_ERROR; + comedi_event(dev, s); + usbduxfast_ai_stop(dev, 0); + } +} + +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 |= cfc_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_EXT | TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (!cmd->chanlist_len) + err |= -EINVAL; + + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->convert_arg, tmp); + } + + /* stop source */ + switch (cmd->stop_src) { + case TRIG_COUNT: + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_NONE: + err |= cfc_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; + } + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + + /* + * 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); + + 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->stop_src == TRIG_COUNT) + devpriv->ai_sample_count = cmd->stop_arg * cmd->scan_end_arg; + else /* TRIG_NONE */ + devpriv->ai_sample_count = 0; + + 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 00000000000..ccc3ef7ba55 --- /dev/null +++ b/drivers/staging/comedi/drivers/usbduxsigma.c @@ -0,0 +1,1715 @@ +/* + * usbduxsigma.c + * Copyright (C) 2011 Bernd Porr, Bernd.Porr@f2s.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: usbduxsigma + * Description: University of Stirling USB DAQ & INCITE Technology Limited + * Devices: (ITL) USB-DUX [usbduxsigma] + * Author: Bernd Porr <BerndPorr@f2s.com> + * Updated: 8 Nov 2011 + * Status: testing + */ + +/* + * 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/usb.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> +#include <asm/unaligned.h> + +#include "comedi_fc.h" +#include "../comedidev.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 + +#define USBDUXSIGMA_NUM_AO_CHAN 4 + +/* 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.65 / 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 */ + uint32_t *in_buf; + /* input buffer for single insn */ + uint8_t *insn_buf; + + unsigned int ao_readback[USBDUXSIGMA_NUM_AO_CHAN]; + + unsigned high_speed:1; + unsigned ai_cmd_running:1; + unsigned ao_cmd_running:1; + unsigned pwm_cmd_running:1; + + /* number of samples to acquire */ + int ai_sample_count; + int ao_sample_count; + /* 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_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_cmd *cmd = &s->async->cmd; + unsigned int dio_state; + uint32_t val; + int ret; + int i; + + /* first we test if something unusual has just happened */ + switch (urb->status) { + case 0: + /* copy the result in the transfer buffer */ + memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF); + 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"); + + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + if (devpriv->ai_cmd_running) { + usbduxsigma_ai_stop(dev, 0); /* w/o unlink */ + /* we are still running a command, tell comedi */ + s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR); + comedi_event(dev, s); + } + return; + + default: + /* + * a real error on the bus + * pass error to comedi if we are really running a command + */ + if (devpriv->ai_cmd_running) { + dev_err(dev->class_dev, + "%s: non-zero urb status (%d)\n", + __func__, urb->status); + usbduxsigma_ai_stop(dev, 0); /* w/o unlink */ + s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR); + comedi_event(dev, s); + } + return; + } + + if (unlikely(!devpriv->ai_cmd_running)) + return; + + urb->dev = comedi_to_usb_dev(dev); + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(ret < 0)) { + dev_err(dev->class_dev, "%s: urb resubmit failed (%d)\n", + __func__, ret); + if (ret == -EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handler\n"); + usbduxsigma_ai_stop(dev, 0); /* w/o unlink */ + s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR); + comedi_event(dev, s); + return; + } + + /* get the state of the dio pins to allow external trigger */ + dio_state = be32_to_cpu(devpriv->in_buf[0]); + + devpriv->ai_counter--; + if (likely(devpriv->ai_counter > 0)) + return; + + /* timer zero, transfer measurements to comedi */ + devpriv->ai_counter = devpriv->ai_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* not continuous, fixed number of samples */ + devpriv->ai_sample_count--; + if (devpriv->ai_sample_count < 0) { + usbduxsigma_ai_stop(dev, 0); /* w/o unlink */ + /* acquistion is over, tell comedi */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + } + + /* 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 */ + + ret = cfc_write_array_to_buffer(s, &val, sizeof(uint32_t)); + if (unlikely(ret == 0)) { + /* buffer overflow */ + usbduxsigma_ai_stop(dev, 0); /* w/o unlink */ + return; + } + } + /* tell comedi that data is there */ + s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); + 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_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_cmd *cmd = &s->async->cmd; + uint8_t *datap; + int ret; + int i; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + if (devpriv->ao_cmd_running) { + usbduxsigma_ao_stop(dev, 0); /* w/o unlink */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + } + return; + + default: + /* a real error */ + if (devpriv->ao_cmd_running) { + dev_err(dev->class_dev, + "%s: non-zero urb status (%d)\n", + __func__, urb->status); + usbduxsigma_ao_stop(dev, 0); /* w/o unlink */ + s->async->events |= (COMEDI_CB_ERROR | COMEDI_CB_EOA); + comedi_event(dev, s); + } + return; + } + + if (!devpriv->ao_cmd_running) + return; + + devpriv->ao_counter--; + if ((int)devpriv->ao_counter <= 0) { + /* timer zero, transfer from comedi */ + devpriv->ao_counter = devpriv->ao_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* not continuous, fixed number of samples */ + devpriv->ao_sample_count--; + if (devpriv->ao_sample_count < 0) { + usbduxsigma_ao_stop(dev, 0); /* w/o unlink */ + /* acquistion is over, tell comedi */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + 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; + + ret = comedi_buf_get(s, &val); + if (ret < 0) { + dev_err(dev->class_dev, "buffer underflow\n"); + s->async->events |= (COMEDI_CB_EOA | + COMEDI_CB_OVERFLOW); + } + *datap++ = val; + *datap++ = chan; + devpriv->ao_readback[chan] = val; + + s->async->events |= COMEDI_CB_BLOCK; + comedi_event(dev, s); + } + } + + 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, + "%s: urb resubmit failed (%d)\n", + __func__, ret); + if (ret == EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handler\n"); + usbduxsigma_ao_stop(dev, 0); /* w/o unlink */ + s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR); + 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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= cfc_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 |= cfc_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 |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + 1000000); + + tmp = (cmd->scan_begin_arg / 1000000) * 1000000; + } + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, tmp); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + err |= cfc_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 (cmd->stop_src == TRIG_COUNT) { + /* data arrives as one packet */ + devpriv->ai_sample_count = cmd->stop_arg; + } else { + /* continuous acquisition */ + devpriv->ai_sample_count = 0; + } + + 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); + + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + 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((uint32_t + *)(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; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + down(&devpriv->sem); + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + up(&devpriv->sem); + + return insn->n; +} + +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; + } + devpriv->ao_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 |= cfc_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 |= cfc_check_trigger_src(&cmd->scan_begin_src, flags); + + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_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 |= cfc_check_trigger_is_unique(cmd->start_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, + 1000000); + + /* not used now, is for later use */ + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 125000); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + err |= cfc_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 (cmd->stop_src == TRIG_COUNT) { + /* not continuous, use counter */ + if (high_speed) { + /* high speed also scans everything at once */ + devpriv->ao_sample_count = cmd->stop_arg * + cmd->scan_end_arg; + } else { + /* + * There's no scan as the scan has been + * handled inside the FX2. Data arrives as + * one packet. + */ + devpriv->ao_sample_count = cmd->stop_arg; + } + } else { + /* continuous acquisition */ + devpriv->ao_sample_count = 0; + } + + 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); + + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + + 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, + "%s: non-zero urb status (%d)\n", + __func__, 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, "%s: urb resubmit failed (%d)\n", + __func__, 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; + } else { + 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((uint32_t *)(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 = USBDUXSIGMA_NUM_AO_CHAN; + 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; + + /* 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 00000000000..0adf3cffddb --- /dev/null +++ b/drivers/staging/comedi/drivers/vmk80xx.c @@ -0,0 +1,963 @@ +/* + 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: K8055/K8061 aka VM110/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/usb.h> +#include <linux/uaccess.h> + +#include "../comedidev.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 +}; + +struct firmware_version { + unsigned char ic3_vers[32]; /* USB-Controller */ + unsigned char ic6_vers[32]; /* CPU */ +}; + +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 firmware_version fw; + struct semaphore limit_sem; + unsigned char *usb_rx_buf; + unsigned char *usb_tx_buf; + enum vmk80xx_model model; +}; + +static int vmk80xx_check_data_link(struct comedi_device *dev) +{ + struct vmk80xx_private *devpriv = dev->private; + struct usb_device *usb = comedi_to_usb_dev(dev); + unsigned int tx_pipe; + unsigned int rx_pipe; + unsigned char tx[1]; + unsigned char rx[2]; + + tx_pipe = usb_sndbulkpipe(usb, 0x01); + rx_pipe = usb_rcvbulkpipe(usb, 0x81); + + tx[0] = VMK8061_CMD_RD_PWR_STAT; + + /* + * Check that IC6 (PIC16F871) is powered and + * running and the data link between IC3 and + * IC6 is working properly + */ + usb_bulk_msg(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval); + usb_bulk_msg(usb, rx_pipe, rx, 2, NULL, HZ * 10); + + return (int)rx[1]; +} + +static void vmk80xx_read_eeprom(struct comedi_device *dev, int flag) +{ + struct vmk80xx_private *devpriv = dev->private; + struct usb_device *usb = comedi_to_usb_dev(dev); + unsigned int tx_pipe; + unsigned int rx_pipe; + unsigned char tx[1]; + unsigned char rx[64]; + int cnt; + + tx_pipe = usb_sndbulkpipe(usb, 0x01); + rx_pipe = usb_rcvbulkpipe(usb, 0x81); + + tx[0] = VMK8061_CMD_RD_VERSION; + + /* + * Read the firmware version info of IC3 and + * IC6 from the internal EEPROM of the IC + */ + usb_bulk_msg(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval); + usb_bulk_msg(usb, rx_pipe, rx, 64, &cnt, HZ * 10); + + rx[cnt] = '\0'; + + if (flag & IC3_VERSION) + strncpy(devpriv->fw.ic3_vers, rx + 1, 24); + else /* IC6_VERSION */ + strncpy(devpriv->fw.ic6_vers, rx + 25, 24); +} + +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 insn_cmd; + int chan; + int cmd; + int reg; + int n; + + insn_cmd = data[0]; + if (insn_cmd != INSN_CONFIG_RESET && insn_cmd != GPCT_RESET) + return -EINVAL; + + down(&devpriv->limit_sem); + + chan = CR_CHAN(insn->chanspec); + + 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; + } + + for (n = 0; n < insn->n; n++) + if (vmk80xx_write_packet(dev, cmd)) + break; + + up(&devpriv->limit_sem); + + return 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 = comedi_board(dev); + 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_WRITEABLE | 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_WRITEABLE; + 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_WRITEABLE; + 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_WRITEABLE; + 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 == VMK8061_MODEL) { + vmk80xx_read_eeprom(dev, IC3_VERSION); + dev_info(&intf->dev, "%s\n", devpriv->fw.ic3_vers); + + if (vmk80xx_check_data_link(dev)) { + vmk80xx_read_eeprom(dev, IC6_VERSION); + dev_info(&intf->dev, "%s\n", devpriv->fw.ic6_vers); + } + } + + 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_VERSION("0.8.01"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/kcomedilib/Makefile b/drivers/staging/comedi/kcomedilib/Makefile new file mode 100644 index 00000000000..3aff8ed08e2 --- /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 00000000000..8777f958c04 --- /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 == NULL) + 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 useable 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 00000000000..91dea25b572 --- /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 00000000000..b6849545b81 --- /dev/null +++ b/drivers/staging/comedi/range.c @@ -0,0 +1,166 @@ +/* + module/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 + range information ioctl + + arg: + pointer to rangeinfo structure + + reads: + range info structure + + writes: + n struct comedi_krange structures to rangeinfo->range_ptr +*/ +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; +} + +static int aref_invalid(struct comedi_subdevice *s, unsigned int chanspec) +{ + unsigned int aref; + + /* disable reporting invalid arefs... maybe someday */ + return 0; + + aref = CR_AREF(chanspec); + switch (aref) { + case AREF_DIFF: + if (s->subdev_flags & SDF_DIFF) + return 0; + break; + case AREF_COMMON: + if (s->subdev_flags & SDF_COMMON) + return 0; + break; + case AREF_GROUND: + if (s->subdev_flags & SDF_GROUND) + return 0; + break; + case AREF_OTHER: + if (s->subdev_flags & SDF_OTHER) + return 0; + break; + default: + break; + } + dev_dbg(s->device->class_dev, "subdevice does not support aref %i", + aref); + return 1; +} + +/** + * 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 || + aref_invalid(s, chanspec)) { + 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); |
